skill-vet — 代理技能 是做什麼的?
在安裝代理技能套件前進行審查,只需一個命令。 當任務涉及來自此機器外部的技能——例如從市集下載、某人分享的儲存庫、客戶傳送的套件——在該套件被安裝到任何代理會載入的位置之前,請使用此工具。
單一命令
skill-vet vet <技能套件資料夾> [--json] [--strict]
(透過 pipx install skill-vet 或 pip install skill-vet 安裝。從原始碼目錄執行時,相同命令為 python3 src/skill_vet.py vet <技能套件資料夾> …。)
- 參數是套件的根資料夾(包含 SKILL.md 的那個)。每次執行僅處理一個套件。
- 退出碼:0 表示判定 CLEAN 或 REVIEW,1 表示判定 BLOCKED(或在
--strict模式下為 REVIEW),2 表示輸入錯誤(資料夾不存在、不是資料夾、錯誤參數)。在--json模式下,每個錯誤(包括參數錯誤)都是一個帶有error鍵的 JSON 物件。絕不會出現 traceback。 - 唯讀,且不執行任何內容。 Vet 以靜態文字讀取套件:永遠不會匯入、執行、shell 調用或求值套件中的任何內容。其原始碼不匯入任何網路或子程序模組。可以安全地指向一個你積極不信任的套件。
危險模型(完整產品)
一個套件僅當其執行以下三種行為之一時才被視為危險——擁有者的既定規則(2026-10-30),嚴格套用:
- 竊取(STEALS) —— 讀取憑證儲存、瀏覽器設定檔、SSH 金鑰或環境秘密,並將結果發送或複製出去。
- 監控(SPIES) —— 捕捉螢幕、鍵盤、麥克風、攝影機或瀏覽器歷史記錄,並持續儲存或發送捕捉內容,而文件未揭露該捕捉行為。
- 隱藏發送(SENDS IN SECRET) —— 網路呼叫,其負載或目的地被隱藏:編碼後的負載發送到未揭露的端點、本地檔案內容發送到未揭露的端點、結合了套件外寫入操作的發送,或者任何來自聲稱可離線運作之套件的發送。
單一模式命中本身並不構成危險判定。 每個測試都需要在同一檔案中有其組合的證據,且發現結果會引用兩部分的檔案和行號。任何未達組合標準的情況都會以警告形式呈現,並陳述證據。組合如下:
| 同一檔案中的證據 | 判定 |
|---|---|
| 秘密讀取 + 發送,或秘密讀取 + 複製/移動到外部路徑 | 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 | 名稱不是以小寫 kebab-case 格式的 slug。 |
BROAD_TOOL_GRANT | WARN | allowed-tools 項目包含 *、Tool(*) 萬用字元或裸 Bash。範圍限定的形式如 Bash(npm run *) 不會被標記。 |
BROKEN_REFERENCE | ERROR | SKILL.md(markdown 連結或反引號路徑)或指令稿(路徑字面值)中引用的本地檔案不存在。解析時會先嘗試引用檔案所在的資料夾,再嘗試套件根目錄。 |
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 | 下載後 pipe 到 shell(`curl … |
HYGIENE_OBFUSCATION | WARN | 文字檔案中存在 base64 區塊(120+ 字元,混合大小寫並包含數字或 +/)、十六進位區塊(120+ 字元)或連續 8+ 個 \xNN 跳脫序列。每種混淆方式每檔案一個發現。 |
CONCEALED_UNICODE | WARN | 文字檔案中存在不可見字元(零寬度、單詞連接符、軟連字符、雙向控制字元、Unicode 標籤字元);會引用計數、首行和字元名稱。開頭的 BOM 是簽名而非隱藏,會被忽略。 |
CONCEALED_BLANK_RUN | WARN | markdown 檔案中出現 50+ 個連續空白行,且其後有內容。 |
HIDDEN_COMMENT | WARN | markdown 檔案中存在 120+ 字元的 HTML 註解。 |
WRITES_OUTSIDE_PACK | WARN | 指令稿寫入絕對路徑、家目錄或系統路徑(/tmp、/etc、~/、$HOME 等)。 |
INSTALL_HOOK | WARN | 安裝時會執行的檔案:setup.py、install.* / setup.sh、.pth 檔案,或包含 preinstall/install/postinstall hook 的 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)、markdown 和其他文字檔案,每個檔案上限 2 MiB。二進位檔案和指向套件外部的符號連結目標只會列出,不會掃描。
- 引用從 SKILL.md 和指令稿檔案中收集。其他 markdown 檔案內的引用(例如 references/*.md 連結到其他檔案)在 v1.0.0 中不會被追蹤。
- 揭露僅針對套件根目錄的 SKILL.md 和 README.md 判斷——這是安裝該套件的人預期應已閱讀的兩個文件。
JSON schema (--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(布林值)、file(或 null)、line(或 null)、message、fixsummary:findings、errors、warnings、info、danger——這些計數應與findings完全一致verdict(CLEAN|REVIEW|BLOCKED)、verdict_rule、limits
發現結果按確定性排序:先危險、再嚴重程度、檔案、行號、規則——相同的套件總是產生相同的報告。
刻意不達成的目標(誠實說明)
- 永遠不執行任何內容。 不會執行、匯入、編譯或解析 shell 指令稿;不會解碼負載(編碼區塊會被報告,但不會被打開)。
- 靜態模式是證據,而非證明。 一個在執行時從無害片段組合其行為、將組合分散到兩個檔案(vet 僅在單一檔案內判斷組合)、或以誠實的明文句子撰寫社交工程的套件,可以通過檢查。CLEAN 判定意味著沒有模式被觸發——README 如此說明,每個報告也是。
- 二進位檔案超出範圍。 綑綁的二進位檔案會被標記,正是因為 vet 對它無從判斷。
- 無遙測、無網路、無寫入。 僅使用標準庫;原始碼中完全沒有網路或子程序程式碼(測試會解析其匯入並證明)。
- 模式表為快照。 新的捕捉函式庫、新的憑證儲存和新的客戶端 API 不斷出現;原始碼中的表格以版本發佈日期為準,而非視為完整。