AI Agent 插件供应链完整性检查
agent-supply-chain
为 Agent 插件生成 SHA-256 清单,与已发布清单比对文件,审查依赖固定版本并记录 dev → staging → production 来源链。
介绍来源: author · GitHub, Inc. · b6483db44fb602654bca6a097185fcaddb305f97 参考来源
作者原文
Verify supply chain integrity for AI agent plugins, tools, and dependencies. Use this skill when: - Generating SHA-256 integrity manifests for agent plugins or tool packages - Verifying that installed plugins match their published manifests - Detecting tampered, modified, or untracked files in agent tool directories - Auditing dependency pinning and version policies for agent components - Building provenance chains for agent plugin promotion (dev → staging → production) - Any request like "verify plugin integrity", "generate manifest", "check supply chain", or "sign this plugin"
SkillPi 解读
- 能完成的任务
- 生成及核验 INTEGRITY.json,识别文件变化并审阅依赖版本策略。
- 适合谁
- 审查 Agent 插件、MCP 服务器及部署来源的维护者。
- 输入和输出
- 输入插件文件、预期清单和依赖元数据;输出完整性比对、依赖审查信息及晋级检查。
- 软件、账号及付费要求
- 示例使用 Python 哈希及文件操作,并按场景结合 CI/依赖工具;未指定必需付费账号。 使用时由宿主核对并授权实际文件读写和外部服务。文档引用域名(非运行白名单):docs.npmjs.com, github.com, owasp.org, slsa.dev
- 限制与区别
- 哈希一致说明文件匹配可信清单,不代表行为必然安全。清单与晋级信任模型仍需审阅,本站未执行该 Skill 的代码。
- 实测状态
- 未测试
权限与使用前检查
根据当前主文档自动整理,命中内容可能是示例或防护说明;不是实际权限声明,也未覆盖附件、外部依赖或运行行为。
-
读取本地文件
限定到你选择的文件或项目目录;避免读取无关的个人资料、密钥和配置文件。
主文档提及 · 第 55, 135, 188, 255, 298 行 -
保存或修改文件
先确认输出目录和同名文件是否会被覆盖;重要文件保留备份,优先写入新的输出目录。
主文档提及 · 第 92 行
文档引用的域名或地址
这些地址可能用于文档链接或示例,不是已批准的联网清单。使用前核对实际目的地及发送的数据。
- docs.npmjs.com
- github.com
- owasp.org
- slsa.dev
安全评级与访问范围
caution · 静态证据核验,不代表运行安全保证。
示例使用 Python 哈希及文件操作,并按场景结合 CI/依赖工具;未指定必需付费账号。 使用时由宿主核对并授权实际文件读写和外部服务。文档引用域名(非运行白名单):docs.npmjs.com, github.com, owasp.org, slsa.dev
使用前请由宿主核对并授权实际文件读写、外部服务和联网目的地;本站的核验不会授予任何运行权限。
原始 SKILL.md
---
name: agent-supply-chain
description: |
Verify supply chain integrity for AI agent plugins, tools, and dependencies. Use this skill when:
- Generating SHA-256 integrity manifests for agent plugins or tool packages
- Verifying that installed plugins match their published manifests
- Detecting tampered, modified, or untracked files in agent tool directories
- Auditing dependency pinning and version policies for agent components
- Building provenance chains for agent plugin promotion (dev → staging → production)
- Any request like "verify plugin integrity", "generate manifest", "check supply chain", or "sign this plugin"
---
# Agent Supply Chain Integrity
Generate and verify integrity manifests for AI agent plugins and tools. Detect tampering, enforce version pinning, and establish supply chain provenance.
## Overview
Agent plugins and MCP servers have the same supply chain risks as npm packages or container images — except the ecosystem has no equivalent of npm provenance, Sigstore, or SLSA. This skill fills that gap.
```
Plugin Directory → Hash All Files (SHA-256) → Generate INTEGRITY.json
↓
Later: Plugin Directory → Re-Hash Files → Compare Against INTEGRITY.json
↓
Match? VERIFIED : TAMPERED
```
## When to Use
- Before promoting a plugin from development to production
- During code review of plugin PRs
- As a CI step to verify no files were modified after review
- When auditing third-party agent tools or MCP servers
- Building a plugin marketplace with integrity requirements
---
## Pattern 1: Generate Integrity Manifest
Create a deterministic `INTEGRITY.json` with SHA-256 hashes of all plugin files.
```python
import hashlib
import json
from datetime import datetime, timezone
from pathlib import Path
EXCLUDE_DIRS = {".git", "__pycache__", "node_modules", ".venv", ".pytest_cache"}
EXCLUDE_FILES = {".DS_Store", "Thumbs.db", "INTEGRITY.json"}
def hash_file(path: Path) -> str:
"""Compute SHA-256 hex digest of a file."""
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
h.update(chunk)
return h.hexdigest()
def generate_manifest(plugin_dir: str) -> dict:
"""Generate an integrity manifest for a plugin directory."""
root = Path(plugin_dir)
files = {}
for path in sorted(root.rglob("*")):
if not path.is_file():
continue
if path.name in EXCLUDE_FILES:
continue
if any(part in EXCLUDE_DIRS for part in path.relative_to(root).parts):
continue
rel = path.relative_to(root).as_posix()
files[rel] = hash_file(path)
# Chain hash: SHA-256 of all file hashes concatenated in sorted order
chain = hashlib.sha256()
for key in sorted(files.keys()):
chain.update(files[key].encode("ascii"))
manifest = {
"plugin_name": root.name,
"generated_at": datetime.now(timezone.utc).isoformat(),
"algorithm": "sha256",
"file_count": len(files),
"files": files,
"manifest_hash": chain.hexdigest(),
}
return manifest
# Generate and save
manifest = generate_manifest("my-plugin/")
Path("my-plugin/INTEGRITY.json").write_text(
json.dumps(manifest, indent=2) + "\n"
)
print(f"Generated manifest: {manifest['file_count']} files, "
f"hash: {manifest['manifest_hash'][:16]}...")
```
**Output (`INTEGRITY.json`):**
```json
{
"plugin_name": "my-plugin",
"generated_at": "2026-04-01T03:00:00+00:00",
"algorithm": "sha256",
"file_count": 12,
"files": {
".claude-plugin/plugin.json": "a1b2c3d4...",
"README.md": "e5f6a7b8...",
"skills/search/SKILL.md": "c9d0e1f2...",
"agency.json": "3a4b5c6d..."
},
"manifest_hash": "7e8f9a0b1c2d3e4f..."
}
```
---
## Pattern 2: Verify Integrity
Check that current files match the manifest.
```python
# Requires: hash_file() and generate_manifest() from Pattern 1 above
import json
from pathlib import Path
def verify_manifest(plugin_dir: str) -> tuple[bool, list[str]]:
"""Verify plugin files against INTEGRITY.json."""
root = Path(plugin_dir)
manifest_path = root / "INTEGRITY.json"
if not manifest_path.exists():
return False, ["INTEGRITY.json not found"]
manifest = json.loads(manifest_path.read_text())
recorded = manifest.get("files", {})
errors = []
# Check recorded files
for rel_path, expected_hash in recorded.items():
full = root / rel_path
if not full.exists():
errors.append(f"MISSING: {rel_path}")
continue
actual = hash_file(full)
if actual != expected_hash:
errors.append(f"MODIFIED: {rel_path}")
# Check for new untracked files
current = generate_manifest(plugin_dir)
for rel_path in current["files"]:
if rel_path not in recorded:
errors.append(f"UNTRACKED: {rel_path}")
return len(errors) == 0, errors
# Verify
passed, errors = verify_manifest("my-plugin/")
if passed:
print("VERIFIED: All files match manifest")
else:
print(f"FAILED: {len(errors)} issue(s)")
for e in errors:
print(f" {e}")
```
**Output on tampered plugin:**
```
FAILED: 3 issue(s)
MODIFIED: skills/search/SKILL.md
MISSING: agency.json
UNTRACKED: backdoor.py
```
---
## Pattern 3: Dependency Version Audit
Check that agent dependencies use pinned versions.
```python
import re
def audit_versions(config_path: str) -> list[dict]:
"""Audit dependency version pinning in a config file."""
findings = []
path = Path(config_path)
content = path.read_text()
if path.name == "package.json":
data = json.loads(content)
for section in ("dependencies", "devDependencies"):
for pkg, ver in data.get(section, {}).items():
if ver.startswith("^") or ver.startswith("~") or ver == "*" or ver == "latest":
findings.append({
"package": pkg,
"version": ver,
"severity": "HIGH" if ver in ("*", "latest") else "MEDIUM",
"fix": f'Pin to exact: "{pkg}": "{ver.lstrip("^~")}"'
})
elif path.name in ("requirements.txt", "pyproject.toml"):
for line in content.splitlines():
line = line.strip()
if ">=" in line and "<" not in line:
findings.append({
"package": line.split(">=")[0].strip(),
"version": line,
"severity": "MEDIUM",
"fix": f"Add upper bound: {line},<next_major"
})
return findings
```
---
## Pattern 4: Promotion Gate
Use integrity verification as a gate before promoting plugins.
```python
def promotion_check(plugin_dir: str) -> dict:
"""Check if a plugin is ready for production promotion."""
checks = {}
# 1. Integrity manifest exists and verifies
passed, errors = verify_manifest(plugin_dir)
checks["integrity"] = {
"passed": passed,
"errors": errors
}
# 2. Required files exist
root = Path(plugin_dir)
required = ["README.md"]
missing = [f for f in required if not (root / f).exists()]
# Require at least one plugin manifest (supports both layouts)
manifest_paths = [
root / ".github/plugin/plugin.json",
root / ".claude-plugin/plugin.json",
]
if not any(p.exists() for p in manifest_paths):
missing.append(".github/plugin/plugin.json (or .claude-plugin/plugin.json)")
checks["required_files"] = {
"passed": len(missing) == 0,
"missing": missing
}
# 3. No unpinned dependencies
mcp_path = root / ".mcp.json"
if mcp_path.exists():
config = json.loads(mcp_path.read_text())
unpinned = []
for server in config.get("mcpServers", {}).values():
if isinstance(server, dict):
for arg in server.get("args", []):
if isinstance(arg, str) and "@latest" in arg:
unpinned.append(arg)
checks["pinned_deps"] = {
"passed": len(unpinned) == 0,
"unpinned": unpinned
}
# Overall
all_passed = all(c["passed"] for c in checks.values())
return {"ready": all_passed, "checks": checks}
result = promotion_check("my-plugin/")
if result["ready"]:
print("Plugin is ready for production promotion")
else:
print("Plugin NOT ready:")
for name, check in result["checks"].items():
if not check["passed"]:
print(f" FAILED: {name}")
```
---
## CI Integration
Add to your GitHub Actions workflow:
```yaml
- name: Verify plugin integrity
run: |
PLUGIN_DIR="${{ matrix.plugin || '.' }}"
cd "$PLUGIN_DIR"
python -c "
from pathlib import Path
import json, hashlib, sys
def hash_file(p):
h = hashlib.sha256()
with open(p, 'rb') as f:
for c in iter(lambda: f.read(8192), b''):
h.update(c)
return h.hexdigest()
manifest = json.loads(Path('INTEGRITY.json').read_text())
errors = []
for rel, expected in manifest['files'].items():
p = Path(rel)
if not p.exists():
errors.append(f'MISSING: {rel}')
elif hash_file(p) != expected:
errors.append(f'MODIFIED: {rel}')
if errors:
for e in errors:
print(f'::error::{e}')
sys.exit(1)
print(f'Verified {len(manifest[\"files\"])} files')
"
```
---
## Best Practices
| Practice | Rationale |
|----------|-----------|
| **Generate manifest after code review** | Ensures reviewed code matches production code |
| **Include manifest in the PR** | Reviewers can verify what was hashed |
| **Verify in CI before deploy** | Catches post-review modifications |
| **Chain hash for tamper evidence** | Single hash represents entire plugin state |
| **Exclude build artifacts** | Only hash source files — .git, __pycache__, node_modules excluded |
| **Pin all dependency versions** | Unpinned deps = different code on every install |
---
## Related Resources
- [OpenSSF SLSA](https://slsa.dev/) — Supply-chain Levels for Software Artifacts
- [npm Provenance](https://docs.npmjs.com/generating-provenance-statements) — Sigstore-based package provenance
- [Agent Governance Toolkit](https://github.com/microsoft/agent-governance-toolkit) — Includes integrity verification and plugin signing
- [OWASP ASI-09: Supply Chain Integrity](https://owasp.org/www-project-agentic-ai-threats/)
版权与许可声明
MIT License Copyright GitHub, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
用户讨论