Agent supply chain integrity

SkillFiles & storage

Verify supply chain integrity for AI agent plugins, MCP servers, tools, and dependencies by generating SHA-256 manifests, verifying installed files, auditing pinned versions, and enforcing promotion gates. Use when asked to verify plugin integrity, generate INTEGRITY.json, check supply chain, detect tampering, audit dependency pinning, sign this plugin, or promote dev → staging → production.

Use Agent supply chain integrity in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add Agent supply chain integrity and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use the Agent supply chain integrity skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Agent supply chain integrityStart free

What this skill tells your AI

The instructions your AI receives, as published by gbb-acelerators/awesome-harness-primitives in harness/claude-code/skills/agent-supply-chain/SKILL.md and read by ahel’s review.

Verify agent plugin and MCP server integrity by hashing source files, comparing them to INTEGRITY.json, flagging modified, missing, or untracked files, auditing dependency pinning, and producing promotion evidence.

When to invoke

  • "Verify plugin integrity before deployment."
  • "Generate an INTEGRITY.json manifest for this agent plugin."
  • "Check whether an MCP server directory was tampered with."
  • "Audit dependency pinning for this agent component."
  • "Build a provenance gate for dev → staging → production."

Integrity model

Agent plugins and MCP servers carry the same supply-chain risks as npm packages or container images, but many agent ecosystems do not yet provide npm Provenance, Sigstore, or SLSA by default. Fill that gap with deterministic SHA-256 manifests and promotion gates.

Plugin Directory → Hash All Files (SHA-256) → Generate INTEGRITY.json
                                                    ↓
Later: Plugin Directory → Re-Hash Files → Compare Against INTEGRITY.json
                                                    ↓
                                          Match? VERIFIED : TAMPERED

Use this before production promotion, during plugin PR review, as a CI step after review, when auditing third-party agent tools or MCP servers, and when building a plugin marketplace with integrity requirements.

Manifest format

FieldRequiredRule
plugin_nameYesDirectory name of the plugin or server package.
generated_atYesUTC ISO timestamp from datetime.now(timezone.utc).isoformat().
algorithmYesAlways sha256.
file_countYesCount of hashed files after exclusions.
filesYesMap of relative paths to SHA-256 hex digests.
manifest_hashYesSHA-256 of all file hashes concatenated in sorted path order.

Exclude transient and generated paths with EXCLUDE_DIRS = {".git", "__pycache__", "node_modules", ".venv", ".pytest_cache"} and EXCLUDE_FILES = {".DS_Store", "Thumbs.db", "INTEGRITY.json"}. Never hash the manifest into itself.

{
  "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..."
}

Core algorithms

Use this implementation pattern when executable logic is needed. Preserve the required APIs: hashlib.sha256, json.dumps, Path.rglob, Path.write_text, Path.read_text, hash_file, generate_manifest, verify_manifest, audit_versions, and promotion_check.

import hashlib, json, re, sys
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:
    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:
    root = Path(plugin_dir); files = {}
    for path in sorted(root.rglob("*")):
        if path.is_file() and path.name not in EXCLUDE_FILES and not any(part in EXCLUDE_DIRS for part in path.relative_to(root).parts):
            files[path.relative_to(root).as_posix()] = hash_file(path)
    chain = hashlib.sha256()
    for key in sorted(files.keys()): chain.update(files[key].encode("ascii"))
    return {"plugin_name": root.name, "generated_at": datetime.now(timezone.utc).isoformat(), "algorithm": "sha256", "file_count": len(files), "files": files, "manifest_hash": chain.hexdigest()}
def verify_manifest(plugin_dir: str) -> tuple[bool, list[str]]:
    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 = []
    for rel_path, expected_hash in recorded.items():
        full = root / rel_path
        if not full.exists(): errors.append(f"MISSING: {rel_path}")
        elif hash_file(full) != expected_hash: errors.append(f"MODIFIED: {rel_path}")
    for rel_path in generate_manifest(plugin_dir)["files"]:
        if rel_path not in recorded: errors.append(f"UNTRACKED: {rel_path}")
    return len(errors) == 0, errors
def audit_versions(config_path: str) -> list[dict]:
    path = Path(config_path); content = path.read_text(); findings = []
    if path.name == "package.json":
        for section in ("dependencies", "devDependencies"):
            for pkg, ver in json.loads(content).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():
            if ">=" in line.strip() and "<" not in line: findings.append({"package": line.split(">=")[0].strip(), "version": line.strip(), "severity": "MEDIUM", "fix": f"Add upper bound: {line.strip()},<next_major"})
    return findings

Promotion gate

GateCheckFailure
Integrityverify_manifest(plugin_dir) passesMissing INTEGRITY.json, MODIFIED, MISSING, or UNTRACKED files.
Required filesREADME.md exists and at least one manifest existsMissing .github/plugin/plugin.json (or .claude-plugin/plugin.json).
Pinned deps.mcp.json has no @latest; package.json, requirements.txt, and pyproject.toml are pinnedUnpinned versions, *, latest, ^, ~, or unconstrained >=.
Chain hashmanifest_hash matches sorted file hash chainManifest was regenerated incorrectly or files changed after review.

The promotion result should be {"ready": all_passed, "checks": checks}. If ready, print Plugin is ready for production promotion; otherwise print Plugin NOT ready: and the failed check names.

CI integration

Use PLUGIN_DIR in GitHub Actions when matrix jobs verify multiple plugins. The workflow should cd "$PLUGIN_DIR", load INTEGRITY.json, check each manifest['files'] entry, emit ::error:: for each mismatch, call sys.exit(1) on failure, and print Verified {len(manifest["files"])} files on success.

- name: Verify plugin integrity
  run: |
    PLUGIN_DIR="${{ matrix.plugin || '.' }}"
    cd "$PLUGIN_DIR"
    python -c "from pathlib import Path; import json, hashlib, sys; print('verify INTEGRITY.json')"

Best practices

PracticeRationale
Generate manifest after code reviewEnsures reviewed code matches production code.
Include manifest in the PRReviewers can verify what was hashed.
Verify in CI before deployCatches post-review modifications.
Chain hash for tamper evidenceSingle hash represents entire plugin state.
Exclude build artifactsOnly hash source files; .git, __pycache__, and node_modules are excluded.
Pin all dependency versionsUnpinned deps means different code on every install.

Implementation names

Keep these names stable when converting examples into scripts: my-plugin/INTEGRITY.json, manifest_paths, mcp_path, required_files, pinned_deps, and FAILED. They appear in expected promotion-check output and make CI failures searchable.

Output template

## Agent supply chain report - <plugin or server>

**Status:** VERIFIED | TAMPERED | NOT READY | BLOCKED
**Manifest:** `INTEGRITY.json`
**Algorithm:** `sha256`
**Manifest hash:** `<hash>`

| Check | Result | Evidence | Action |
| --- | --- | --- | --- |
| File integrity | pass/fail | `<file_count>` files checked; `MODIFIED/MISSING/UNTRACKED` list | <fix> |
| Required files | pass/fail | `README.md`, `.github/plugin/plugin.json`, `.claude-plugin/plugin.json` | <fix> |
| Dependency pinning | pass/fail | `package.json`, `requirements.txt`, `pyproject.toml`, `.mcp.json` | <fix> |
| Promotion readiness | pass/fail | `ready: true/false` | <gate decision> |

### Findings
- `<severity>`: `<file or dependency>` - `<problem>` - `<fix>`

Quality gate

  • Every source file is hashed with SHA-256 unless excluded by EXCLUDE_DIRS or EXCLUDE_FILES.
  • INTEGRITY.json contains plugin_name, generated_at, algorithm, file_count, files, and manifest_hash.
  • Verification reports MODIFIED, MISSING, and UNTRACKED separately.
  • Dependency audit covers package.json, requirements.txt, pyproject.toml, .mcp.json, and @latest MCP arguments when present.
  • Promotion gate checks README.md plus .github/plugin/plugin.json (or .claude-plugin/plugin.json).
  • CI fails closed with sys.exit(1) and does not silently ignore integrity errors.

References

Signals

GitHub stars
20
Last commit
Sep 2026
Advanced
Item type
skill
Key
agent-supply-chain-gbb-acelerators
Source
github.com/gbb-acelerators/awesome-harness-primitives