AI 套件供應鏈有一個發生在安裝前的斷點:Agent 可能把「看起來像官方文件」的文字交給 npm 或 PyPI。HTTPS 在正常憑證驗證下保護你與該網域來源之間的連線;它不會把文件中的套件名稱綁定到 registry publisher。當 Claude、Codex 或其他可用終端機的 Agent 把資料轉成命令,這條身分缺口就會進入執行面。
2026 年 8 月 26 日,研究者 Alon Hertz 公開一項他們稱為受控安全研究的實驗:團隊在 PyPI 與 npm 註冊少量掃描當下尚未被註冊的名稱,並稱放入只用於回報安裝事件的 beacon;Ars Technica 另報導,部分回呼還帶有程序父子鏈。研究者稱,之後收到被其歸因於新創與大型企業環境的回呼。依目前公開的回呼與程序鏈說法,這支持「官方文件 → 公開 registry → 套件執行 → 對外連線」可能走通;但套件名稱、受影響公司與原始回呼紀錄未公開,AlphaLab 無法獨立核對回呼歸屬與完整因果。公開資料也不證明單純閱讀 llms.txt 就會執行、不證明使用者一定未核准,或研究者取得憑證、建立持久化、入侵所有被掃描網站。
這篇會用一組只供 parse-only scanner 使用、故意不符合本文 policy 的合成 fixture,帶你把六道閘門放進 CI。全程不下載真實可疑樣本、不公布被隱去的套件名稱,也不把新聞事件當成產品漏洞排行榜。你會得到的是一套設計成可套用於 llms.txt、README、網頁與工單內容的安裝前方法。
先說結論:AI 套件供應鏈要看三種證據、過六道閘門
🛡️ 記憶把手:安裝信任 = 文件來源 × 套件身分 × 不可變成品。
文件網域是真的、套件卻對不上預期發布者,整體仍是零;套件名稱對了、版本與 artifact 沒固定,也不能進正式 workspace。任何一項無法證明,就先 HOLD,而不是請 Agent 猜。
六關依序是:把命令降級成資料、驗套件與 release 身分、驗 artifact 與 provenance、由 CI 執行硬政策、在無秘密隔離區暫存、留下不可覆寫收據。它與《Claude Code Plugin 安全驗收》都談供應鏈,但本文只處理「文件指令如何綁到 registry 套件與 release artifact」,不處理 Plugin、Marketplace、MCP、Hooks 或移除流程。

llms.txt 是資料,不是安裝授權
llms.txt 目前是一項社群提案:網站用 Markdown 整理適合語言模型閱讀的摘要與連結。單獨讀取這份文字不等於執行其中命令;執行來自後續消費系統的工具與權限。相同決策鏈也可能從普通網頁、README、issue 或漏洞回報出現;即使刪除 llms.txt,也無法消除其他外部文字進入這條鏈的可能性。
請把「官方文件」與「官方套件」拆成兩個問題。前者問:這段文字是否由預期網域送來?後者問:registry 上這個名稱、這個版本、這個發布身分與這份成品,能否回到你事先獨立知道的 organization、repository 與 build workflow?在這起事件裡,unowned 指掃描當下尚未被註冊或已可重新認領的公開目的地;它不等於惡意,也不代表今天仍無人持有。
原理:先切斷「文字直接進 shell」的捷徑
本文採用的架構改動,是把 Agent 從 installer 降級成 requester。Agent 可以閱讀文件並產生 package-request.json;只有獨立的 policy broker 能查 registry、建立 lock、在隔離環境暫存,最後把通過的 manifest 與收據交回專案。這是本文自訂的參考架構,不是 Claude Code 或 Codex 內建的 package workflow。流程可以寫成:
untrusted docs
-> parse-only scanner
-> package-request.json
-> policy + human approval
-> secretless quarantine
-> verified lock + receipt
-> real workspace
這與《Prompt Injection 回歸測試》的核心一致:提示詞可以提醒,但硬邊界要由工具權限、網路與 CI 決定。以下六關全部以「正式 workspace 尚未執行候選套件」為前提。
AI 套件供應鏈第 1 關:只解析,不執行
痛點:掃描器若把抓到的字串交給 shell,自己就成了風險。解法:只讀檔、比對字串、輸出 JSON;程式內不得出現 subprocess、os.system 或 registry request。下面的 candidate finder 只抽出文件名稱、文件 SHA-256、ecosystem 與原始 token,再交給後續結構化 parser。
import hashlib, json, re
from pathlib import Path
RULES = {
"npm": re.compile(r"\bnpm\s+(?:install|i)\s+([^\s#]+)"),
"npx": re.compile(r"\bnpx(?:\s+--yes)?\s+([^\s#]+)"),
"pypi": re.compile(r"\b(?:python\s+-m\s+pip|pip)\s+install\s+([^\s#]+)")
}
for path in map(Path, ["llms.txt", "README.md"]):
text = path.read_text(encoding="utf-8")
digest = hashlib.sha256(text.encode()).hexdigest()
for ecosystem, rule in RULES.items():
for hit in rule.finditer(text):
print(json.dumps({"doc": path.name, "sha256": digest,
"ecosystem": ecosystem,
"token": hit.group(1)}, ensure_ascii=False))
這段程式只是教學用 candidate finder,只涵蓋畫面上的三種基本形狀;它不是完整 shell parser,也不是 production policy gate,遇到 option、引號、續行、多套件或 requirements file 可能漏抓或誤抓。CI 使用的後續 parser 必須覆蓋所有允許語法,並把無法唯一解析的 install-like line 標成 AMBIGUOUS/REJECT。名稱正規化、精確版本、用途、文件 URL 與 approval request 也由後續 parser 補齊;任何輸出都不得交給 shell。
安全失敗測試可使用 fixture.invalid!agent-sdk;驚嘆號讓它在本政策中直接成為非法 token。以下是 registry-invalid、只供 parse-only scanner 讀取的文字 fixture;不得貼入 shell,也不得送往 registry:
npm install fixture.invalid!agent-sdk
npx --yes fixture.invalid!agent-init
python -m pip install fixture.invalid!agent-sdk
AlphaLab 在 2026 年 8 月 31 日用 parse-only scanner 讀取兩個本機 fixture,找到三筆 reference,三筆都因「不在 allowlist/沒有精確版本/token 非法」被 REJECT;npx 那筆再多觸發 bare-npx 規則。過程沒有 shell 呼叫、網路 request、套件下載或程式執行。這個測試驗的是 gate 會失敗,不是重現攻擊。
第 2 關:套件名稱要綁回預期擁有者
痛點:registry 的 homepage/repository 欄位是發布或上傳時提供的 metadata,應視為待核對主張,而不是獨立身分證據。解法:先從公司官網、既有正式 repository 或內部資產清單取得 expected_source,再讀取精確版本的 registry metadata,做雙向核對。
npm view 'PACKAGE_NAME@1.2.3' \
name version maintainers repository.url time deprecated \
scripts bin dist.integrity dist.tarball --json \
--registry=https://registry.npmjs.org/
curl --fail --silent --show-error --location \
'https://pypi.org/pypi/PACKAGE_NAME/1.2.3/json'
npm view 官方文件提醒,省略版本時預設會看 latest,因此不能省略 @1.2.3。PyPI 的 JSON API可提供 release 檔案、SHA-256、上傳時間與 project URLs,但 PyPI 也說明 metadata 不一定與成品內資料一致。實務上至少核對:名稱與 namespace、目前的 maintainers、官網到 repo 的回鏈、repo release/tag、發布時間、deprecated/yanked 狀態,以及預期 builder;維護者是否變動,必須再與先前保存的 registry snapshot/audit receipt 比較。任何關係只能由同一份 README 解釋,就先 HOLD。
第 3 關:固定版本、artifact 與 provenance
痛點:名稱與 repository 對得上,不代表這次下載的 bytes 就是該 source/build 產出的成品。解法:把精確版本、lockfile、artifact digest、registry signature 與 attestation 綁在同一筆准入決策。SLSA 1.2把 provenance 定義為可追溯成品「在哪裡、何時、如何產生」的可驗證資訊;它回答來源,不替程式碼安全背書。
npm 有一個容易誤寫的限制:npm audit signatures 要先有由 npm install 或 npm ci 建立的 dependency tree。因此它不是「下載前驗證」;正確做法是在第 5 關的無秘密隔離區,用 reviewed lock 執行 npm ci --ignore-scripts,再跑:
npm audit signatures \
--json \
--include-attestations \
--registry=https://registry.corp.example/
這個命令會驗 registry signatures 與現有 attestation,但官方成功範例也顯示「通過簽章的套件數」可以高於「有 provenance attestation 的套件數」。npm registry signature 證明 registry 所簽的 package name、version 與 integrity 關係,不是 publisher 身分背書;broker 還要逐一要求 direct dependency 的 attestation,並把其中的 repository/builder 與獨立的 expected_source 比對,不能只看 exit code。
PyPI 可在安裝前,用 broker image 裡事先固定版本的 verifier 檢查單一 wheel。事先固定 verifier 是本文的 bootstrap rule,不是 PyPI 強制要求:
pypi-attestations verify pypi \
--repository 'https://github.com/EXPECTED_OWNER/EXPECTED_REPOSITORY' \
'https://files.pythonhosted.org/.../PACKAGE_NAME-1.2.3-...whl'
PyPI 官方流程會核對 Trusted Publisher、預期 repository 與 wheel bytes;但官方安全模型同樣強調:attestation 告訴你套件從哪裡來,不等於你應該信任它。若 policy 還要求特定 workflow、ref 或 builder,必須解析 provenance bundle 並另做 allowlist 比對;--repository 只完成官方文件描述的 repository identity 檢查。本文也不臨時用待驗的 pip 安裝 verifier,以免形成 bootstrap 迴圈。
AI 套件供應鏈第 4 關:把政策寫進 CI,不靠 Agent 自律
痛點:「每次安裝前先問我」仍可能被 CI 無互動模式或一次誤按繞過。解法:policy 先於 shell,預設拒絕可直接抓取並執行的入口。npm 的 npx/npm exec 文件說明,本機沒有候選 package 時會下載到 npm cache 後執行;stdin 不是 TTY 或偵測到 CI 時,提示會假定為 --yes。因此,對來自不可信文件、且無法證明候選已存在於受審 local dependency tree 的 bare npx/npm exec,本文 policy 預設 REJECT;已鎖定的本機 binary 應走另一條可判分 allow rule。
deny:
- untrusted-external bare-npx | npm-exec | pipx-run | curl-pipe-shell
- latest | version-range | git | url | local-path | editable
- extra-index | global-install | sudo
require:
- exact-name-and-version
- independently-known-repository-and-builder
- reviewed-complete-lock-and-artifact-hashes
- human-approval-bound-to-request-digest
人工核准也要核准「哪一份資料」:request digest、文件 SHA、套件座標、lock digest、artifact hashes、預期 identity 與例外理由缺一不可。只顯示一行自然語言「要安裝某某套件嗎?」不是供應鏈證據。若要把掃描結果輸出成 CI 可讀格式,可接著參考《AI-Infra-Guard 掃描教學》的 fail-closed 設計。
第 5 關:只准隔離 broker 暫存,正式 workspace 不碰候選程式
痛點:驗證 npm signatures 必須先建立 dependency tree;某些 metadata/wheel 解析也會連網。解法:把這個必要步驟放進可銷毀、無秘密、無主機家目錄掛載的低權限容器或 VM。source checkout 唯讀,只有 dependency 目錄可寫;出口只開內部 registry、必要的 signature/provenance endpoint 與外部 audit sink。以下 npm 參數以 broker image 內固定的 npm 12 為準。
# npm:只在隔離區,依已審 lock 暫存
npm ci \
--ignore-scripts \
--audit=false \
--allow-directory=none \
--allow-file=none \
--allow-git=none \
--allow-remote=none \
--registry=https://registry.corp.example/
# PyPI:在全新空 venv 中,只從已核准的唯讀 wheelhouse 安裝
python -m pip --isolated --require-virtualenv --no-cache-dir install \
--no-index --find-links=/approved/read-only-wheelhouse \
--only-binary=:all: --require-hashes \
--requirement requirements.lock
pip 的 secure installs 文件要求 hash-checking mode 下直接與間接 dependency 都固定版本並提供雜湊。雜湊應來自已核准成品,不要只把同一個遠端 index 回傳的值原封不動當成獨立信任依據;requirements.lock 在這裡只是 requirements-file 的檔名,不代表獨立標準格式。npm 的 --ignore-scripts 會抑制 package.json scripts,現行文件也說它會停用 .npm-extension;但日後 import、CLI entry point,以及明確呼叫的 npm run/npm test 仍會執行程式,所以測試繼續留在同一隔離區。
Claude Code sandbox會把限制套用到 Bash 與 child processes(包括 npm);failIfUnavailable: true 阻止啟動時退回 unsandboxed,allowUnsandboxedCommands: false 則另外關閉命令失敗後的 unsandboxed retry escape hatch。廣泛的 allowedDomains、停用檔案隔離或允許 unsandboxed retry 都會擴大暴露面。Codex將 sandbox 與 approval policy 分成不同控制;官方文件把 sandbox_mode="danger-full-access" 加 approval_policy="never" 定義為 full access。兩份官方文件描述的是命令的檔案、網路與 approval/permission 邊界;package namespace、publisher、source repo 與 artifact 的關係,仍需由本文 registry/provenance policy 另行核對。因此兩層應疊加。更完整的權限分層可看《AI Agent Runtime 權限控制》。
--registry 是 resolver 設定,不是 egress firewall;仍要在 OS/container 網路層 enforce allowlist。內部 npm proxy 也必須保留 upstream dist.signatures、provenance attestations,並提供相容的 signing-key endpoint,否則第 3 關的 signature verification 不能成立。
第 6 關:收據要不可覆寫,失敗也要留痕
痛點:一個可任意改寫的本機 receipt.jsonl 只能方便除錯,不能證明歷史未被改過。解法:把每關結果送到 Agent 與 quarantine worker 都無刪改權的獨立目的地。object store 必須明確配置 immutable retention,例如 S3 Object Lock compliance mode;external SIEM 只有在另設不可改 retention 與獨立管理權限時才算。hash chain 提供的是 tamper evidence,不會單獨阻止刪除或截斷,還要把簽章 checkpoint 錨定到 writer 無法控制的位置。NIST SSDF SP 800-218 v1.1也把第三方元件驗證、開發環境保護與 release component provenance 納入安全開發工作。
最低收據欄位包括:request ID、時間、文件 URL 與 SHA、抽出的字串(只存 inert text)、normalized name/version、預期 repo/builder、完整 dependency closure、artifact URLs 與 hashes、lock digest、signature/attestation 結果、sandbox image digest、網路政策、審核人、PASS/HOLD/REJECT 與例外。不要記錄 token、Authorization header、環境秘密或完整 credentials。
怎麼做故障測試:每一關都要能真的變紅
不要用惡意樣本測 gate。用第 1 關的非法 fixture,加上純 metadata 與本機檔案即可覆蓋以下情境:
- 名稱/版本:沒有精確版本、名稱不在 allowlist、token 含非法字元,預期 REJECT。
- 直接執行:不可信外部文字出現無法對應受審本機 tree 的 bare
npx/npm exec、pipx run或 pipe-to-shell,預期 REJECT。 - 身分:package metadata 的 repo 與
expected_source不同,預期 HOLD 或 REJECT。 - 成品:lock digest、artifact hash、signature 或 expected builder 任一不符,預期 REJECT。
- 隔離:worker 要求 secret、主機 home、提權或未核准網域,預期 REJECT。
- 稽核:在本文 policy 中,受外部保護的 immutable/tamper-evident audit sink 無法寫入,預期整次准入失敗,不能只在本機補記。
最後再做一個正向 fixture:只使用內部 allowlist 既有套件、精確版本、預先核准 repo、已保存的成品 hash 與 lock。只有它應該 PASS。若負向案例全綠,或正向案例靠手動關閉規則才過,policy 還不能上線。
PASS、HOLD、REJECT 要怎麼選?
PASS 代表六關對同一份 request digest 全部成立,允許的也只是指定版本與成品;不是永久認證。HOLD 代表關係尚未證明,例如官方 repo 可確認、歷史 release 卻沒有 policy 要求的 attestation,需要指定審核人做有期限的例外。REJECT 則是已違反硬規則,例如浮動版本、未知擁有者、由不可信文字帶入且未對應受審 local tree 的 bare npx、hash 不符、要求秘密或 unrestricted egress。
policy broker 應把 document、publisher、repository、lock、artifact 與 policy 的 digest 納入同一決策或可驗證連結;其中任一輸入改變,都必須強制重新判定。不要把上週的 PASS 套到今天的 latest;也不要把隔離區的 node_modules 或 virtualenv 直接搬進正式環境。只 promotion 經審核的 manifest、lock、hash、provenance bundle 與決策收據。
常見錯誤:六個看似安全、其實只回答一半的做法
- 「網址是官方 HTTPS」:只驗文件傳輸來源,沒有驗 registry namespace。
- 「有 SHA-256」:只固定 bytes;若預期身份一開始就錯,固定的是錯的成品。
- 「有簽章/provenance」:證明某身份與 build chain,沒有判斷該身份或程式是否值得信任。
- 「關掉 install scripts」:降低暫存階段風險,沒有阻止日後由專案載入的程式碼或 CLI entry point。
- 「Agent 每次都會問」:互動式提示不是 unattended CI 的硬政策;
npm exec在 stdin 非 TTY 或偵測到 CI 時會假定--yes。 - 「有 JSONL log」:可改寫的本機檔不是 append-only 稽核紀錄。
FAQ:llms.txt、npm、PyPI 與 Agent 安裝安全
llms.txt 本身是惡意程式嗎?
llms.txt 格式本身只是 Markdown,不是可執行程式;但個別檔案仍可能含錯誤或惡意指令。要進入執行鏈,仍須由後續元件——Agent、人類或其他自動化程式——把文字交給 package manager 或 shell。
刪掉 llms.txt 就能解決嗎?
不能消除整類決策風險。同樣的套件字串仍可能從 README、普通文件、issue、工單或 email 進入後續元件;要修的是「外部文字不能直接進 shell」的權限邊界。
官方網站上的 install command 可以直接信嗎?
不應直接執行。先把文件網域與 registry 套件身分分開驗證,再固定版本、repo、builder、artifact 與 lock。
lockfile 加 hash 就夠了嗎?
不夠。它能固定解析結果與 bytes,卻沒有單獨回答「這份成品是否來自我預期的 owner/repository/builder」。
有 provenance 的 package 就安全嗎?
不一定。provenance 是來源與 build 證據,不是惡意碼掃描、source review 或行為 sandbox 的替代品。
公司是否應該全面封鎖 npm 與 PyPI?
不必把「全部封鎖」當唯一答案。較可操作的做法是讓 Agent 無法直接安裝,所有 dependency 經內部 registry/wheelhouse、精確 lock、隔離 broker 與人工例外流程。
Claude/Codex 的 sandbox 可以取代這六關嗎?
不能取代。Claude/Codex 官方文件描述的是命令的檔案、網路與 approval/permission 邊界;package namespace、publisher、source repo 與 artifact 的關係,仍要由本文的 registry/provenance policy 核對。因此兩層應疊加。
今天只能先做一件事,該做什麼?
先擋來自外部文字、且未證明已存在受審 local tree 的 bare npx/npm exec,並禁止 Agent 把外部文字直接交給 shell。要求它只輸出含文件 SHA、精確套件座標與預期 repo 的結構化 request,便能切斷一條高風險捷徑。
給新手的 6 個重點
llms.txt是資料;執行能力來自 Agent 的工具與權限。- 官方文件網域與 registry package ownership 是兩個不同信任物件。
- AI 套件供應鏈的把手是:文件來源 × 套件身分 × 不可變成品。
- 在本文參考架構中,Agent 只提申請,policy broker 才查詢、隔離暫存與 promotion。
- hash、signature、provenance、source review 與 sandbox 各自處理不同威脅。
- 每個 gate 都要有負向 fixture;在本文 policy 中,不能寫入受外部保護的 audit sink 就 fail closed。
接著閱讀
左右滑動查看更多推薦
結語:讓文件提供線索,不要讓它取得安裝權
這次事件真正值得帶走的,不是「所有 llms.txt 都危險」,而是資料一旦能驅動工具,文件來源就不能代替套件身分。回到本文的記憶把手:安裝信任 = 文件來源 × 套件身分 × 不可變成品。六關的作用,就是讓每一項都留下可重驗、可失敗的證據。
下一步先把現有 Agent 的 npx、pip install 與 npm install 呼叫列成 inventory,再用本文的非法 fixture 確認 CI 會 REJECT。若你想從權限、狀態、工具與驗證迴圈完整搭建自己的執行層,可接著讀《AI Agent Harness 實作教學》,或到 AlphaLab AI 課程把這套方法放進真實工作流。






