<div align="center">
Assay
AI 아티팩트를 평가하기 위한 개방적이고 재현 가능한 프레임워크<br>
skills · MCP 서버 · 에이전트 · 플러그인
CI
License
Node
</div>
모든 AI 아티팩트를 배포하는 레지스트리는 "이것은 어디서 왔는가?"라는 질문에 답하지만, 아무도 "실제로 무엇을 하는가?"를 공개적이고 감사 가능한 신호로 답변하지 않습니다.
Assay는 두 번째 질문에 답합니다. 이는 아티팩트 내부의 내용을 읽고, 선택적으로 실제 모델을 사용한 샌드박스에서 실행해보고 어떤 결과를 도출했는지 판단한 후 — 그 결과를 당신을 믿지 않는 사람도 확인할 수 있는 보고서로 게시합니다.
curl -fsSL https://raw.githubusercontent.com/metahub-ai/assay/main/install.sh | sh
assay run anthropics/skills//skills/pdf
목차
Why
이 간극은 부주의한 것이 아닙니다. 공식 MCP 레지스트리는 소비자들에게 "최소에 가깝거나 없는 수준의 검열(minimal-to-no moderation)"을 가정해야 하며, "저품질이거나 버그가 있는 서버" 또는 "보안 취약점이 있는 서버"를 제거하지 않을 것임을 서면으로 명시하고 있습니다. 대신 이는 "업스트림 패키지 레지스트리 (NPM, PyPI, Docker 등) 또는 다운스트림 하위 레지스트리"에 의존합니다.
한편 업계가 의존하는 모든 신뢰 계층은 시대에 뒤떨어진 반례를 가지고 있습니다.
출처(Provenance) — postmark-mcp가 15개의 깨끗한 버전을 배포했다가, v1.0.16에서 모든 이메일을 공격자에게 BCC하는 한 줄을 추가했습니다. 제거되기 전까지 1,643건의 다운로드를 기록했습니다.<sub>Koi Security · The Hacker News · Postmark's advisory</sub>
평판(Reputation) — SmartLoader 캠페인은 다섯 개의 가짜 GitHub 계정을 구축하여 서로 포크시키고, 이를 통해 트로이 목마화된 Oura MCP 서버 주변에 커뮤니티를 인위적으로 조성했습니다. 이후 이 규모는 7,600개의 레포지토리로 확장되었으며, 그중 약 800개가 Skills 또는 MCP 서버인 척 위장하고 있었습니다.<sub>The Hacker News · Straiker · FakeGit campaign</sub>
정적 분석(Static analysis) — Self-Extracting Skill 패킹은 테스트된 9개 스캐너 모두를 우회했으며, ≥90%의 탐지율을 기록했고, 6개의 정적 스캐너 중 5개에서 99.8%에 달하는 수치를 보였습니다.**<sub>[
curl -fsSL https://raw.githubusercontent.com/metahub-ai/assay/main/install.sh | sh
도메인, 패키지 레지스트리, sudo가 필요 없습니다. GitHub Releases에서 릴리스 tarball을 가져와서, 그 옆에 게시된 sha256과 비교하고, ~/.assay에 압축 해제합니다. 먼저 읽어보시는 것을 추천합니다 — 대신 sh로 파이프하기보다 less로 파이프하세요.
이것이 작동하는 이유는 도구가 런타임 종속성이 없기 때문입니다: 자체 포함된 JavaScript의 약 240 KB tarball입니다. 모든 모델 어댑터는 해당 공급업체의 HTTP API에 직접 연결하므로, 새 설치만으로 키(key) 하나만 있으면 행동 계층(behavioral tier)을 실행할 수 있습니다 — 나중에 추가해야 할 SDK가 없습니다. e2b와 sigstore는 선택적 피어(peer)로 남아 있으며, E2B 샌드박스나 키 없는 서명(keyless signing)을 사용할 때만 지연 로딩(lazily imported)됩니다.
<details>
<summary>다른 설치 방법</summary>
npx github:metahub-ai/assay run .
...
Node 20+가 모든 경우에 필요합니다. 제거는 rm -rf ~/.assay입니다.
</details>
빠른 시작(Quickstart)
설정, 계정, API 키가 필요 없습니다. 오프라인에서도 약 1초 만에 작동합니다.
대상 지정 방법 (What you point it at)
먼저 클론할 필요가 없습니다. 아래의 모든 줄은 실제 공개 아티팩트입니다:
assay run .
assay run ~/.claude/skills/canvas-design
assay run anthropics/skills//skills/pdf
...
레지스트리에서 (From a registry)
assay run metahub:skill/warden
metahub:skill/warden 해석 중
skill warden v0.1.0이 c79f4eb9b7b9에 고정됨
커밋 c79f4eb9b7b9 가져오는 중
레지스트리는 아티팩트 바이트를 호스팅하는 것이 아니라, 그것들이 어디에 있는지 그리고 어떤 커밋을 서비스하고 있는지를 명명하는 카탈로그 항목을 보유합니다. 마지막 부분이 바로 이를 통해 해결해야 하는 이유입니다: assay run owner/repo//path는 요청하는 순간의 main 브랜치에 있는 어떤 것이든 점수를 매기는데, 이는 아무도 설치한 것이 아닙니다. metahub:은 평가를 소비자가 실제로 받는 커밋에 고정하므로, 점수와 설치가 어긋나지 않게 합니다. 또한 레지스트리는 아티팩트의 종류에 대해서도 권위적이어서 감지가 추측이 아니게 됩니다.
셀프 호스팅 배포를 위해 ASSAY_REGISTRY를 설정하세요; registry:는 벤더 중립적인 별칭으로 작동합니다. 비공개 및 목록에 없는 아티팩트는 도달할 수 없으며, 리졸버는 이를 누락되었다고 보고하는 대신 그렇게 말합니다.
GitHub의 /tree/ URL은 사람들이 클립보드에 실제로 가지고 있는 것이므로, 참조(ref)와 하위 디렉토리는 거기에서 바로 읽어옵니다.
<details>
<summary>왜 이중 슬래시(`//`)를 사용하며, 다른 두 가지 규칙은 무엇인가요</summary>
//는 레포지토리와 그 내부의 경로를 분리합니다. 단일 슬래시는 할 수 없습니다. 왜냐하면 git 서버가 /network.git/a와 같이 레포지토리를 합법적으로 호스팅할 수 있기 때문입니다. 이것이 Terraform이 //를 선택한 이유이며, 중첩된 GitLab 네임스페이스가 존재하게 되자 degit이 가능한 모든 분리 지점을 무차별 대입(brute-forcing)했던 이유입니다.
owner/repo/skills/pdf는 추측하는 대신 두 가지 수정 모두로 거부됩니다.
#은 참조(ref)를 표시합니다 (@도 허용됩니다). 이것은 URL 프래그먼트이므로 전체 URL과 구성되며 SSH의 user@host와 충돌하지 않습니다. npm, npx, degit, Yarn 모두 이렇게 합니다.
기존 로컬 디렉토리가 항상 우선합니다. src/utils는 유효한 GitHub 단축어이자 그럴듯한 폴더입니다. 누군가 로컬 경로를 의도했는데 낯선 사람의 레포지토리를 조용히 클론하는 것이 훨씬 더 나쁜 실패이므로, 디스크가 승리합니다; 원격(remote)을 강제하려면 gh:owner/repo를 사용하세요.
</details>
원격 타겟은 임시 디렉터리로 가져와지며, 실행이 끝나면 제거되고, 보고서는 요청한 브랜치가 아닌 **해결된 커밋(resolved commit)**을 기록합니다. 즉, '당일의 main이 무엇이었는지'에 대한 점수는 재현할 수 없습니다.
npm 패키지는 레지스트리 자체의 무결성 해시와 비교되며, 감사하려는 패키지에서 prepare 스크립트를 실행할 수 있는 npm pack으로는 절대 가져오지 않습니다.
출력 읽기
pdf skill
1cbcfa643929c208… · b29e7cf65e5cb78a5ac33d582270551bc74a14eb
...
이름 아래의 두 줄은 콘텐츠 다이제스트(content digest)와 해결된 커밋입니다.
실패는 경고보다 먼저, 경고는 통과보다 먼저 오며, 증거는 터미널이나 편집기가 점프로 인식하는 방식인 경로:줄 형식으로 출력됩니다.
커버리지(Coverage)는 모든 축 옆에 인쇄되며 절대 숨겨지지 않습니다. 아무것도 측정하지 않은 축은 대시(-)로 표시되고, 아티팩트가 얻었다고 보이는 '0'이 아닌 _측정되지 않음(not measured)_이라는 단어로 표시됩니다. 전역 하한선 아래에는 헤드라인 숫자가 전혀 없습니다:
OVERALL 점수를 게시하기에 충분히 측정된 부분이 부족합니다
집계가 의미를 가지려면 너무 많은 검사가 건너뛰어졌습니다.
더 많은 기능(예: --net, --behavioral)을 부여하면 커버리지가 높아집니다.
NO_COLOR 환경 변수 아래에서는 색상이 사라지며, stdout이 터미널이 아닐 때와 TERM=dumb인 경우에 해당됩니다. 진행 상황은 stderr로 가기 때문에 --json을 jq로 파이프해도 깨끗하게 유지됩니다. ASSAY_ASCII=1은 박스 드로잉(box-drawing)과 글리프를 ASCII로 대체합니다.
종료 코드: 0은 차단되는 실패가 없음 · 1은 차단 검사가 실패했거나 점수가 --min-score보다 낮은 경우 · 2는 실행을 완료할 수 없었음.
assay setup이 실행될 때까지, assay run은 기본적으로 오프라인(offline) 상태입니다. 이는 네트워크, 모델 또는 샌드박스 접근 권한을 부여하지 않으므로, 읽지 않은 코드에 지정해도 안전합니다. 이러한 기능이 필요한 검사는 조용히 누락되기보다는 감소된 커버리지를 보고합니다.
아티팩트 실행
위의 모든 것은 정적(static)입니다. 이 부분은 아티팩트를 실행하고 그것이 무엇을 하는지 판단하는 부분입니다. 비용과 시간이 소요됩니다. 작은 평가 모델(judge model)을 사용하면 아티팩트당 대략 몇 센트가 들고, 시간은 15초에서 2분 사이가 걸립니다.
assay setup
assay run ~/.claude/skills/canvas-design --transcripts ./transcripts
설정(Configuring)하는 것이 옵트인입니다. setup이 샌드박스와 모델을 선택한 후에는, 행동 평가(behavioral evaluation)가 일반적인 assay run의 일부가 됩니다. 다시 요청할 필요가 없습니다. 두 가지 예외 사항은 모두 의도적입니다:
- 터미널 없음(No terminal), 기본값 아님(no default). CI 환경, 파이프라인 또는 TTY가 없는 모든 곳에서는 여전히 옵트인이며
--behavioral을 전달해야 합니다. 누군가가 한 번 설정하여 자격 증명(credentials)을 상속받은 파이프라인이 커밋당 조용히 청구되고 빌드마다 분 단위의 시간을 추가해서는 안 됩니다.
- 차단되는 안전 실패(blocking safety failure)가 이를 무효화합니다. Assay는 자체 정적 검사에서 악성으로 플래그 지정된 아티팩트를 실행하지 않습니다.
--behavioral은 이를 재정의합니다. 샌드박스는 샌드박스답게 작동해야 하기 때문입니다. 하지만 요청해야 합니다.
반면에 --no-behavioral은 환경이 어떻게 설정되었든 간에 이 기능을 건너뛰며, assay setup은 기본적으로 꺼져 있기를 바랄 경우 그 답변을 기록합니다. assay doctor는 현재 어떤 상황인지 보고합니다.
setup은 제공업체(provider)에게 키를 저장하기 전에 검증하고, 어디에 저장되었는지(~/.assay/config.json, 모드 0600) 알려주며, 환경 변수가 이미 이를 가리고 있을 때 경고합니다. 환경 변수는 항상 우선합니다: CI는 이런 방식으로 비밀(secrets)을 설정하며, 오래된 파일이 회전된 CI 비밀을 조용히 덮어쓰는 것은 찾기 어려운 실패 사례입니다.
<details>
<summary>행동 계층(behavioral tier) 작동 방식</summary>
드라이버 모델(driver model)은 아티팩트의 문서화를 시스템 프롬프트로 받고, 테스트 케이스 세트를 받습니다. 이 테스트 케이스는 저자가 배포했다면 evals/*.json에서 읽어오고, 그렇지 않다면 문서화로부터 합성됩니다. 드라이버는 실제 도구 호출을 사용하여 컨테이너 내부에서 아티팩트를 구동합니다. 이후 모든 기록(transcript)은 별도의 심사 모델(separate judge model)에 의해 루브릭(rubric)과 비교하여 점수가 매겨지고, 심사 모델이 무엇을 말했는지와 관계없이 캡처된 명령어 전체에 대해 결정론적 안전 검사(deterministic safety scan)가 실행됩니다. rm -rf를 간과하는 모델은 이를 승인할 수 없습니다.
기본적으로 세 가지 적대적 탐지(adversarial probes)가 추가됩니다 (프롬프트 주입, 범위 확장, 파괴적인 요청). 이들은 개별적으로 점수가 매겨지며 주요 점수에는 포함되지 않습니다. 이러한 탐지에 거부하는 것이 통과 기준이며, 아티팩트가 올바르게 작동할 경우 탐지의 차원별 점수는 정의되지 않습니다.
--sandbox podman은 로컬에서 무료로 사용할 수 있습니다. --sandbox e2b는 클라우드에서 실행되며 E2B_API_KEY가 필요합니다. 이 프레임워크는 아티팩트 코드를 프로세스 내(in-process)에서 절대 실행하지 않으며, 보고서는 어떤 샌드박스와 어떤 모델이 점수를 산출했는지 기록합니다.
모델 키는 ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY 또는 Ollama/vLLM/LM Studio용 LOCAL_LLM_BASE_URL에서 가져옵니다. 여러 개가 설정된 경우, Assay는 선택을 거부하고 --provider를 요청합니다. 조용히(silently) 선택하는 것은 게시된 점수가 주변 상태에 의존하게 만들기 때문입니다.
드라이버는 모델이므로 케이스당 하나의 샘플은 노이즈가 많습니다. --repeat k는 각 케이스를 k번 실행하고 점수와 함께 신뢰 구간(confidence interval)을 보고합니다.
판결 재현하기 (Replaying a verdict)