skill-vet — 에이전트 스킬 검증 도구은(는) 무엇을 하나요?
에이전트 스킬 팩을 설치하기 전에 한 번의 명령으로 검증합니다. 이 작업에 이 스킬이 관련될 때 사용하세요 — 마켓플레이스에서 다운로드하거나, 누군가 링크한 저장소, 클라이언트가 보낸 팩 등 — 에이전트가 로드할 위치에 팩이 설치되기 전에 말이죠.
한 줄 명령어
skill-vet vet <skill-pack-folder> [--json] [--strict]
(pipx install skill-vet 또는 pip install skill-vet으로 설치합니다.
소스 체크아웃에서는 동일한 명령어가 python3 src/skill_vet.py vet <skill-pack-folder> …입니다.)
- 인자는 팩의 루트 폴더(SKILL.md가 있는 폴더)입니다. 한 번에 하나의 팩만 실행합니다.
- 종료 코드: 0 평결 CLEAN 또는 REVIEW, 1 평결 BLOCKED (또는
--strict가 있는 REVIEW), 2 입력 오류(폴더 누락, 폴더 아님, 잘못된 인자).--json모드에서는 모든 오류(인자 오류 포함)가error키를 가진 하나의 JSON 객체로 출력됩니다. traceback은 절대 발생하지 않습니다. - 읽기 전용이며 아무것도 실행하지 않습니다. Vet은 팩을 정적 텍스트로 읽습니다: 팩을 import, 실행, 셸 아웃 또는 평가하지 않습니다. 자체 소스는 네트워크나 서브프로세스 모듈을 import하지 않습니다. 적극적으로 신뢰하지 않는 팩을 가리켜도 안전합니다.
위험 모델 (제품 전체)
팩은 다음 세 가지 중 하나를 수행할 때만 위험합니다 — 소유자의 고정 규칙(2026-10-30), 정확히 적용됨:
- 도난 — 자격 증명 저장소, 브라우저 프로필, SSH 키 또는 환경 시크릿을 읽고 결과를 보내거나 복사합니다.
- 스파이 — 화면, 키보드, 마이크, 카메라 또는 브라우저 기록을 캡처하고 캡처를 유지하거나 보내며, 문서에 캡처가 공개되지 않습니다.
- 비밀 전송 — 페이로드나 대상이 숨겨진 네트워크 호출: 공개되지 않은 엔드포인트로의 인코딩된 페이로드, 공개되지 않은 엔드포인트로의 로컬 파일 내용 전송, 팩 외부에 쓰기와 결합된 전송, 또는 오프라인에서 작동한다고 주장하는 팩에서의 모든 전송.
패턴 적중만으로 위험 평결이 내려지지 않습니다. 각 테스트는 동일한 파일 내에서 증거 조합이 필요하며, 발견 사항은 파일과 라인을 모두 인용합니다. 조합에 미치지 못하는 것은 증거를 명시한 경고입니다. 조합은 다음과 같습니다:
| 한 파일 내 증거 | 평결 |
|---|---|
| 시크릿 읽기 + 전송, 또는 시크릿 읽기 + 외부 경로로 복사/이동 | DANGER_STEALING (BLOCKED) |
| 캡처 + (전송 또는 유지), SKILL.md / README.md에 캡처 종류 미공개 | DANGER_SPYING (BLOCKED) |
| 전송 + 인코딩된 페이로드, 엔드포인트 미공개 또는 비리터럴 | DANGER_HIDDEN_SENDING (BLOCKED) |
| 전송 + 팩 외부 파일 읽기, 엔드포인트 미공개 | DANGER_HIDDEN_SENDING (BLOCKED) |
| 전송 + 팩 외부 쓰기, 엔드포인트 미공개 | DANGER_HIDDEN_SENDING (BLOCKED) |
| 전송 + 문서가 오프라인/로컬 전용이라고 주장 | DANGER_HIDDEN_SENDING (BLOCKED) |
| 시크릿 읽기, 파일 내 이그레스 없음 | SECRET_ACCESS (WARN) |
| 문서에 언급되지 않은 엔드포인트로 전송, 은닉 조합 없음 | UNDISCLOSED_NETWORK (WARN) |
| 확인할 리터럴 URL 없이 전송 | DYNAMIC_ENDPOINT (WARN) |
| 공개된 엔드포인트로 인코딩된 페이로드 | ENCODED_PAYLOAD (WARN) |
| 캡처 + 유지/전송, 문서에 캡처 공개됨 | DISCLOSED_CAPTURE (WARN) |
| 캡처, 유지 또는 전송 없음 | CAPTURE_CAPABILITY (WARN) |
판단 기준, 공개적으로 명시됨:
- 도난에는 은닉이 필요하지 않습니다: 문서화된 엔드포인트로 자격 증명을 보내는 것은 여전히 도난입니다 — 문서가 인정한다고 해서 안전해지지 않습니다. 공개는 스파이 및 비밀 전송을 정직한 대응과 구분할 뿐입니다.
- 동일한 파일에서 STEALING 발견의 이그레스 절반으로 이미 판단된 전송은 비밀 전송으로 두 번 계산되지 않습니다.
- 캡처 "공개"는 SKILL.md / README.md의 일반 단어(스크린샷, 키스트로크, 마이크, 웹캠, 브라우저 기록 등)에 대해 종류별로 확인됩니다. 캡처하고 이를 명시하는 팩은 인지하고 수용할 기능이며, 스파이가 아닙니다.
- 이그레스 요소는 주석을 포함한 모든 라인에서 파일별로 수집됩니다(보수적: 기능 증거가 주석 근처에 있다고 해서 무시되지 않음). 위생 검사(shell=True, eval, download-exec)는 전체 라인 주석을 건너뜁니다 — 패턴을 언급하는 주석은 문서이지 코드가 아닙니다.
규칙 표
| 규칙 | 심각도 | 근거 |
|---|---|---|
MISSING_SKILL_MD | ERROR | 팩 루트에 SKILL.md 없음 (대소문자만 다른 이름은 SKILL_MD_CASE, WARN, 해당 이름 사용). |
FRONTMATTER_MISSING | ERROR | SKILL.md에 --- 블록 없음. |
FRONTMATTER_PARSE | ERROR | 지원되는 하위 집합(아래 참조)을 벗어난 프론트매터 라인; 해당 라인이 인용됨. |
REQUIRED_FIELD | ERROR | name 또는 description 누락 또는 비어 있음. |
NAME_DIR_MISMATCH | WARN | 정규화된 프론트매터 이름 ≠ 폴더 이름. |
NAME_FORMAT | WARN | 이름이 소문자 케밥-케이스 슬러그가 아님. |
BROAD_TOOL_GRANT | WARN | allowed-tools 항목이 *, Tool(*) 와일드카드 또는 단순 Bash인 경우. Bash(npm run *)와 같은 범위가 지정된 형식은 플래그되지 않음. |
BROKEN_REFERENCE | ERROR | SKILL.md(마크다운 링크 또는 백틱 경로) 또는 스크립트(경로 리터럴)에서 참조된 로컬 파일이 존재하지 않음. 참조 파일의 폴더를 먼저 확인한 다음 팩 루트를 확인함. |
REFERENCE_OUTSIDE_PACK | WARN | 팩 폴더 외부로 확인되는 참조. |
LARGE_FILE | WARN | 1 MiB(1,048,576 바이트)를 초과하는 파일. |
BUNDLED_BINARY | WARN | 바이너리 파일(알려진 바이너리 확장자 또는 처음 8 KiB에 NUL 바이트). 바이너리는 정적으로 검증할 수 없음. |
SYMLINK_OUT | INFO | 대상이 팩을 벗어나는 심볼릭 링크; 대상은 스캔되지 않음. |
DANGER_STEALING | ERROR, 위험 | 위 조합 표 참조. |
DANGER_SPYING | ERROR, 위험 | 위 조합 표 참조. 캡처 종류: 화면, 키보드, 오디오, 카메라, 기록(한 파일 내 기록 저장소 읽기 + 데이터베이스 읽기). |
DANGER_HIDDEN_SENDING | ERROR, 위험 | 위 조합 표 참조. |
SECRET_ACCESS | WARN | 동일 파일에 이그레스 없이 자격 증명 저장소/환경 시크릿 읽기. 시크릿 소스: .ssh / 키 파일, .aws, .gnupg, .netrc, 저장된 자격 증명 파일, 브라우저 저장소 및 프로필 폴더, .env 파일, 시크릿(TOKEN / SECRET / PASSWORD / API_KEY / …)을 명명하는 환경 접근, 전체 환경 읽기. |
UNDISCLOSED_NETWORK | WARN | 엔드포인트 호스트가 SKILL.md 또는 README.md에 나타나지 않는 네트워크 호출. 호스트(또는 해당 상위 도메인)가 문서에 URL로 나타나면 공개된 것으로 간주됨. |
DYNAMIC_ENDPOINT | WARN | 파일 어디에도 리터럴 URL이 없는 네트워크 호출. |
ENCODED_PAYLOAD | WARN | 인코더의 출력이 공개된 엔드포인트로의 전송에 도달함. "도달"은: 인코더 호출이 전송의 호출 세그먼트 내에 있거나, 인코더에서 할당된 변수가 세그먼트에서 사용됨으로 판단됨. |
DISCLOSED_CAPTURE | WARN | 캡처 + 유지/전송, 종류별로 공개됨. |
CAPTURE_CAPABILITY | WARN | 파일에 유지 또는 전송이 없는 캡처 API. |
HYGIENE_SHELL_TRUE | WARN | 코드 라인에 shell=True / shell: true. |
HYGIENE_DYNAMIC_EXEC | WARN | 코드 라인에 eval( / exec( / os.system( / os.popen( / __import__(. |
HYGIENE_DOWNLOAD_EXEC | WARN | 셸로 파이프된 다운로드(`curl … |
HYGIENE_OBFUSCATION | WARN | 텍스트 파일에서 base64 blob(120자 이상, 대소문자 혼합 및 숫자 또는 +/ 포함), hex blob(120자 이상), 또는 8개 이상의 연속 \xNN 이스케이프. 종류별로 파일당 하나의 발견. |
CONCEALED_UNICODE | WARN | 텍스트 파일의 보이지 않는 문자(폭 없음, 단어 결합자, 부드러운 하이픈, 양방향 제어, 유니코드 태그 문자); 개수, 첫 번째 줄 및 문자 이름 인용됨. 선행 BOM은 서명이지 은닉이 아니므로 무시됨. |
CONCEALED_BLANK_RUN | WARN | 내용 뒤에 50줄 이상의 연속 빈 줄이 있는 마크다운 파일. |
HIDDEN_COMMENT | WARN | 마크다운 파일에서 120자 이상의 HTML 주석. |
WRITES_OUTSIDE_PACK | WARN | 스크립트가 절대 경로, 홈 경로 또는 시스템 경로(/tmp, /etc, ~/, $HOME, …)에 쓰기. |
INSTALL_HOOK | WARN | 설치 시 실행되는 파일: setup.py, install.* / setup.sh, .pth 파일, 또는 preinstall/install/postinstall 훅이 있는 package.json. |
평결: BLOCKED = 하나 이상의 위험 발견 → exit 1. REVIEW = 위험 없음, 그러나 하나 이상의 ERROR 또는 WARN → exit 0 (--strict 사용 시 1). CLEAN = ERROR 또는 WARN 없음 (INFO는 남을 수 있음) → exit 0. REVIEW는 의도적으로 exit 0을 반환합니다: REVIEW는 "사람이 읽어야 함"을 의미하며, 설치를 차단하는 스크립트는 경고가 아닌 위험에서 실패해야 합니다 — 이에 동의하지 않는 사람을 위해 --strict가 존재합니다.
프론트매터 하위 집합
파서는 스킬 팩이 실제로 제공하는 프론트매터 형태를 읽습니다: key: value 스칼라(따옴표 제거), key: [a, b] 인라인 목록, key: 뒤에 - item 블록 목록, 그리고 이전 스칼라로 접히는 들여쓰기된 연속 라인. 의도적으로 완전한 YAML 파서가 아닙니다. 하위 집합을 벗어난 라인은 해당 라인이 인용된 FRONTMATTER_PARSE 오류입니다 — vet은 추측하는 대신 읽을 수 없는 것을 보고합니다.
스캔 범위
- 팩의 모든 파일은 크기와 함께 목록화됩니다.
.git및__pycache__디렉터리는 건너뜁니다. - 텍스트 스캔은 스크립트(확장자 또는 shebang 기준), 마크다운 및 기타 텍스트 파일을 대상으로 하며, 파일당 최대 2 MiB까지 스캔합니다. 바이너리 파일 및 팩 외부의 심볼릭 링크 대상은 목록화되며 스캔되지 않습니다.
- 참조는 SKILL.md 및 스크립트 파일에서 수집됩니다. 다른 마크다운 파일 내의 참조(예: references/*.md가 다음으로 연결)는 v1.0.0에서 추적되지 않습니다.
- 공개는 팩 루트의 SKILL.md 및 README.md에 대해서만 판단됩니다 — 팩을 설치하는 사람이 읽었을 것으로 예상되는 두 문서입니다.
JSON 스키마(--json)
하나의 객체:
tool,version,commandpack:dir(전달된 대로),dirname,name(프론트매터 이름 또는 null),skill_filefiles[]:path,bytes,kind(script|markdown|text|binary|other),binary,scanned,symlink_outfindings[]:rule,severity(ERROR|WARN|INFO),danger(bool),file(또는 null),line(또는 null),message,fixsummary:findings,errors,warnings,info,danger—findings와 정확히 일치하는 개수verdict(CLEAN|REVIEW|BLOCKED),verdict_rule,limits
발견 사항은 결정적으로 정렬됩니다: 위험 우선, 그 다음 심각도, 파일, 라인, 규칙 — 동일한 팩은 항상 동일한 보고서를 생성합니다.
의도적 비목표(정직함)
- 아무것도 실행하지 않습니다. 어떤 스크립트도 실행, import, 컴파일 또는 셸 파싱되지 않습니다. 페이로드는 디코딩되지 않습니다(인코딩된 blob은 보고되지만 열리지 않음).
- 정적 패턴은 증거이지 증명이 아닙니다. 런타임에 무해한 조각에서 동작을 조립하거나, 두 파일에 걸쳐 조합을 분할하거나(vet은 파일별로 조합을 판단함), 평범하고 정직한 문장으로 사회 공학을 작성하는 팩은 통과할 수 있습니다. CLEAN 평결은 패턴이 발생하지 않았음을 의미합니다 — README에 명시되어 있으며 모든 보고서에서도 그렇습니다.
- 바이너리는 범위 밖입니다. 번들된 바이너리는 vet이 그것에 대해 아무 말도 할 수 없기 때문에 정확히 플래그됩니다.
- 텔레메트리, 네트워크, 쓰기 없음. 표준 라이브러리만 사용합니다. 소스에는 네트워크 또는 서브프로세스 코드가 전혀 포함되지 않습니다(테스트에서 imports를 파싱하여 증명함).
- 패턴 테이블은 스냅샷입니다. 새로운 캡처 라이브러리, 새로운 자격 증명 저장소 및 새로운 클라이언트 API가 지속적으로 등장합니다. 소스의 테이블은 릴리스 날짜 기준이며 완전한 것으로 취급되지 않습니다.