Skip to content

About

Agent Bill of Materials (AgentBOM) covering Agentic AI Supply Chain (Data, Model, Tool, Registries, Skills, A2A/MCP Servers) Supports and Extends Cyclone 1.7 DX and SPDX 3.0

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

167 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Nexus SymBiote: an Agent Bill of Materials for AI agent supply chains

Nexus SymBiote

Know what your AI agents are built from. Gate them on evidence. Catch drift before it ships.

CI CodeQL Python 3.11 | 3.12 | 3.13 | 3.14 MIT license Status: alpha Exports: CycloneDX 1.7, SPDX 3.0.1, SARIF, VEX

Quick start · Why · What · How · Docs · Status

Not related to Symbiote, the Linux malware of the same name reported in 2022.

Nexus SymBiote generates an Agent Bill of Materials (AgentBOM) — an evidence-backed inventory of the MCP servers, models, instructions, retrieval sources, A2A cards, IDE settings and container images an AI agent is assembled from. It reads a project without running it, flags supply-chain risk, fails CI on drift in what it records, and exports to CycloneDX 1.7, SPDX 3.0.1, SARIF and VEX.

nexus-symbiote scan ./my-agent --no-project-policy -o agentbom.json    # inventory, offline
nexus-symbiote risk agentbom.json --no-project-policy --threshold 50   # gate on risk
nexus-symbiote diff reviewed.json agentbom.json --fail-on-alert        # gate on drift

Alpha. Interfaces, formats and policy semantics may change. Install the published release only after verifying its build attestation, or install a reviewed commit from source; see Install a verified release and Install from reviewed source. This release has known detection gaps: what the collectors do not record, no gate or diff can alert on.

Quick start

Install from reviewed source

Requires git and Python 3.11 to 3.14 on Linux. Install exactly the commit you reviewed, with the hash-locked runtime:

git clone https://github.com/Hunzee-VC/Nexus-Symbiote.git
cd Nexus-Symbiote
git checkout --detach REVIEWED_COMMIT_SHA
python3 -m venv .venv && . .venv/bin/activate
python -m pip install --require-hashes --only-binary=:all: -r requirements-action.lock
python -m pip install --no-deps --no-build-isolation .
nexus-symbiote --version

Replace REVIEWED_COMMIT_SHA with the full commit you reviewed; right after the clone, git rev-parse HEAD prints the latest commit on main. Optional extras: .[sigstore] for Sigstore verification, which the lock already includes; .[cyclonedx] installs the CycloneDX Python library, which the test suite uses to cross-check exports (the tool itself does not import it); .[all] for both. For development: make install-locked (see CONTRIBUTING.md).

Try it in 60 seconds

From the checkout, inventory the bundled demo agent, a small synthetic support assistant. Nothing is executed and no network is used:

nexus-symbiote scan examples/demo-agent -o demo.aibom.json
nexus-symbiote summary demo.aibom.json
nexus-symbiote risk demo.aibom.json --no-project-policy
nexus-symbiote validate demo.aibom.json --project examples/demo-agent --no-project-policy

The scan records 11 components across 9 types and ten findings (risk score 37, below the default fail threshold of 50):

Severity Component Finding
high memory MCP server Unpinned package reference (@latest)
high models/intent-classifier.safetensors No verified, artifact-bound signature
high ollama/ollama:latest container image No immutable digest pin
medium notes and memory MCP servers stdio transport: each runs as a local process
medium product-notes retrieval source No declared content-filtering control
low notes and tickets MCP servers, support-research-assistant A2A card, product-notes retrieval source Values not bound without a source-binding key: a launch argument, endpoint and source URI paths

risk passes its gate. validate exits 1 on purpose: completeness passes, but the model's integrity stays unknown because no signer policy was supplied. An inventory can be complete and still not be approvable.

To approve the model, pin its digest in a signer policy you own, kept outside the scanned project. Here the digest comes from the checkout, which only shows the mechanism; in real use, take it from a source you trust:

digest="$(sha256sum examples/demo-agent/models/intent-classifier.safetensors | cut -d' ' -f1)"
printf '{"version": 2, "artifacts": [{"kind": "digest-pin", "expected_sha256": "sha256:%s",
  "artifact_path": "models/intent-classifier.safetensors"}]}\n' "$digest" > signers.json
nexus-symbiote validate demo.aibom.json --project examples/demo-agent --no-project-policy \
  --signature-policy signers.json

validate now exits 0. The four low findings go away when NEXUS_SYMBIOTE_SOURCE_BINDING_KEY is set, as in Gate drift in CI. The examples walkthrough adds drift detection, exports, a ShadowScan import and a comparison with a synthetic runtime trace, all offline.

Why

AI agents are assembled from parts you did not write, and most of those parts never appear in a software bill of materials.

An agent's behavior depends on its configuration as much as its code — MCP servers, model weights, instruction files, retrieval sources, agent endpoints, IDE permissions — and each can change without a code review. Package SBOMs don't cover these. The OWASP Top 10 for Agentic Applications lists agentic supply-chain vulnerabilities (ASI04) among the top risks.

Risk What it looks like What Nexus SymBiote records
Floating dependencies MCP server at @latest; image without a digest Unpinned-version and missing-digest findings
Tool rug pulls A tool's description or schema changes after review Fingerprints of the tools a live observation records; a changed definition is a critical drift alert. A static scan records tools declared in source by name and file digest
Endpoint repointing An MCP or A2A endpoint moves to another host Critical or high drift alerts. An MCP endpoint with ${...} expansion or an ambiguous host is a gap; a same-host path change needs the binding key
Scope widening An A2A card's OAuth scope goes from read:only to admin High drift alert on the card's capabilities
Executable model files Pickle checkpoint; a .safetensors file that is not one Format classification without loading; SafeTensors header validation
Unsigned weights Model file with no artifact-bound signature UNKNOWN integrity until your signer policy verifies it
Hidden instructions Bidi or zero-width characters in CLAUDE.md; text in an HTML comment Hidden-character findings in every text file the prompt collector reads, and hidden-comment findings in its Markdown and suffix-less files
Over-broad permissions Claude Code in bypassPermissions mode, granted Bash(*), or given a hook command Permission and hook findings; a digest of each settings file the collector recognizes, so a changed setting is a drift alert. In Codex config.toml, .cursor/cli.json and the Antigravity, xAI and Copilot files, a change only to a value the redaction removes, such as a token or a URL path, alerts only with the binding key; a command or operator glued to such a value, and a host glued to it after an operator or another URL scheme, alert without it
Credential exposure Tokens in MCP env blocks or connection strings Variable names, never values; known secret patterns redacted

Every row has limits, listed by surface in the known limitations. The trust model, design principles and offline-by-default enforcement are in the security model.

What

An AgentBOM: one evidence-backed inventory of an agent, with gates, drift checks and standard exports.

How it works

             ┌──────────────────────────────────────────────┐
  agent      │ 8 static collectors: nothing is executed     │
  project ──>│ mcp · model · prompt · rag · registry        │
 (read-only) │ a2a · ide_agents · shadowscan                │
             └──────────────────────┬───────────────────────┘
                                    v
             ┌──────────────────────────────────────────────┐
             │ AgentBOM (native JSON)                       │
             │ components · relationships · findings        │
             │ coverage, gaps and assurance for every claim │
             └──────┬───────────────┬───────────────┬───────┘
                    v               v               v
             validate · risk       diff          exports
             gates, exit 0/1   drift alerts   CycloneDX · SPDX
                                               SARIF · VEX
  1. Collect. Each collector reads its surfaces through confined, size-bounded file reads and returns components, findings and coverage status.
  2. Assemble. Results merge into one AgentBOM. Every claim keeps its assurance level: declared, observed or verified.
  3. Gate. validate checks completeness and integrity; risk scores findings against your policy. Gaps the collectors report fail closed; surfaces they do not support are not reported.
  4. Compare. diff compares two snapshots semantically and rates each change.
  5. Export. Standard formats are generated from the same collection, so every export describes the same evidence.

What it inventories

Eight built-in collectors read the project's configuration and artifacts, and the mcp, rag and a2a collectors also search its source code for SDK and library references. Nothing they read is executed, imported or loaded. Select a subset with --scanner; collectors you leave out are recorded as unknown, not clean.

Surface Collector Records
MCP client configuration, launcher resolver settings, server source mcp Clients, servers, launchers, package identities and pins, transports, endpoints, tool fingerprints
Model weights, with their configuration, tokenizer and custom code model SHA-256 of every weight file, serialization safety, SafeTensors structure, signature status
Instruction, rule, skill, command and subagent files prompt Content digests; hidden-character and hidden-comment findings
RAG configuration, root .env key names, vector-store imports rag Retrieval sources: store, index and embedding identities, URI origins, control states
Package manifests and lockfiles, Dockerfiles, Compose files registry Packages and images in the AI package catalog, pins, lockfile integrity, image digests, project settings that redirect a package to another registry or index
A2A agent cards, client configuration, SDK references a2a Endpoint and interface origins, security schemes and scopes, card signatures (recorded, not verified)
Settings of Claude Code, Codex, Cursor, Gemini CLI, Windsurf, Cline, Copilot and others ide_agents Configured agents, permission modes, blanket grants, hooks, a digest of each settings file it recognizes
Nexus-ShadowScan reports shadowscan Imported discovery claims, kept separate from local evidence; CI can require a signed report

The collector reference lists every path each collector reads, the coding-agent settings that raise findings, and the registry collector's gaps in requirements files and Dockerfiles. The registry collector and model integrity pages describe those two collectors in full. An optional agent.yaml manifest at the project root names the agent and declares further instruction files and models.

What it checks

Command What it answers
validate Did every collector finish without reporting a gap? Do hashes, signatures and source bindings still match?
risk What are the findings, and does the score or worst severity cross your policy?
diff What changed in the recorded inventory since the reviewed snapshot, and how much does each change matter?
digest Do two inventories record the same content, wherever and whenever each was collected? Equal digests say nothing about who produced either file (reproducible inventories)
verify-attestation Did keys you trust sign exactly this inventory, or its content, for this purpose? Not whether it is accurate, complete or the newest one (inventory attestation)
trace-diff Do the agent's own OpenTelemetry traces report a tool, model, agent or MCP endpoint that the inventory does not declare? Traces are reported evidence: a missing span proves nothing
minimum-elements Which of the 50 G7 AI SBOM minimum elements does an inventory's CycloneDX export carry, and how do you supply the rest? Coverage, not compliance
crosswalk Which OWASP, MITRE ATLAS, NIST AI RMF, EU AI Act, ISO/IEC 42001 and CWE entries do the findings bear on? A reading aid, not a compliance verdict
verify-artifact, verify-discovery Do these bytes match a trusted digest, Sigstore identity or ShadowScan report?
blast-radius Which agents and components are associated with a compromised component?
runtime keygen/sign/verify/digest/key-id/register Are signed runtime contracts valid, current and canonical? register retires older snapshots; only the gateway checks revocation and approval bindings
discover-mcp, verify-live, advisories What does a running server actually serve? Do exact versions have known advisories? (live mode)

Each check sees only what the collectors record, and trace-diff also what the traces report: surfaces the collectors do not support are neither gaps nor compared (known limitations). The CLI reference documents every command and option.

What it produces

Output How to get it Use it for
Native AgentBOM JSON scan (default) Full-fidelity record; defined by the AgentBOM schema
CycloneDX 1.7 scan -f cyclonedx SBOM tooling; validated against the pinned official schema
SPDX 3.0.1 scan -f spdx SPDX tooling; validated against the pinned official schema
SARIF 2.1.0 scan -f sarif GitHub code scanning and other SARIF viewers
CycloneDX VEX vex Findings marked in_triage, with no exploitability claim
Graph view graph agentbom.json -o graph.html A self-contained offline page of components, associations, risk classes and exposure paths, or JSON with --format json (graph view)
Framework crosswalk crosswalk agentbom.json --format markdown Findings grouped by the OWASP, MITRE ATLAS, NIST AI RMF, EU AI Act, ISO/IEC 42001 and CWE entries they bear on, as Markdown or JSON (framework crosswalk)
Attestation envelope attest agentbom.json --key reviewer.pem --purpose reviewed --out agentbom.json.att A DSSE envelope over an in-toto Statement v1 that signs the file's exact bytes, checked offline with verify-attestation (inventory attestation)
Trace evidence trace-diff agentbom.json traces.json -o trace-evidence.json Declared versus reported runtime use as JSON, and SARIF with --sarif; it never raises a component's assurance (trace evidence)
ShadowScan simple list export-inventory agentbom.json --format shadowscan-simple --resource-prefix PREFIX -o approved.json ShadowScan's --inventory: the reviewed repository agent, approved for the repository's root project only (ShadowScan approval)

Standard exports embed the native inventory in a digest-bound extension; keep it to recover full semantics. Without it, a consumer never sees a conditional edge as an unconditional relationship, and sees coverage as incomplete or unknown, never complete. Standard exports maps every field, including SARIF rules and what VEX can express. Exports written by builds before 0.1.0 may need to be regenerated; see fingerprint migration.

What a scan does not prove

A scan is an inventory, not a verdict. It does not show that a model is benign, that a tool's backend matches its description, or that every agent action was mediated. Read what results do and do not establish before using results in an approval process. Treat exported BOMs as sensitive.

How

Install a verified release, inventory your agent, then let CI hold the line.

Install a verified release

Requires Python 3.11 to 3.14 on Linux, git, curl and the GitHub CLI signed in with gh auth login. Releases are built from main by the release workflow and published to PyPI through OIDC trusted publishing with build attestations. (0.1.0 is the latest published PyPI release while 0.2.0 is in preparation on main; to run 0.2.0 before publication, use Install from reviewed source.) The block resolves the v0.1.0 tag to its commit, downloads the exact wheel, verifies its attestation against that commit, and only then installs the hash-locked runtime from the same commit and the wheel, without resolving anything else:

(
  set -euo pipefail
  python3 -m venv --clear .venv
  commit="$(git ls-remote https://github.com/Hunzee-VC/Nexus-Symbiote.git 'refs/tags/v0.1.0^{}' | cut -f1)"
  test -n "$commit" || { echo "tag v0.1.0 not found" >&2; exit 1; }
  .venv/bin/python -m pip download --no-deps --only-binary=:all: nexus-symbiote==0.1.0 -d dist
  gh attestation verify dist/nexus_symbiote-0.1.0-py3-none-any.whl --repo Hunzee-VC/Nexus-Symbiote \
    --signer-workflow Hunzee-VC/Nexus-Symbiote/.github/workflows/release.yml \
    --source-ref refs/heads/main --source-digest "$commit" --deny-self-hosted-runners
  curl -fsSL -o requirements-action.lock \
    "https://raw.githubusercontent.com/Hunzee-VC/Nexus-Symbiote/$commit/requirements-action.lock"
  .venv/bin/python -m pip install --require-hashes --only-binary=:all: -r requirements-action.lock
  .venv/bin/python -m pip install --no-deps dist/nexus_symbiote-0.1.0-py3-none-any.whl
  .venv/bin/nexus-symbiote --version
)

Then activate the environment with . .venv/bin/activate. The wheel does not include the demo agent; run the demo from a checkout.

  • Paste the block as one unit, unchanged. The subshell rebuilds .venv and stops at the first failing command, such as a missing or signed-out gh or a missing tag. Placing it before && or || makes bash ignore its set -e, and running the lines one at a time loses these checks.
  • What the block establishes. Nothing is installed unless the release workflow attested these exact wheel bytes for the commit the tag points at, and the lock file comes from that commit. The version check runs last, inside .venv, so an earlier install on your PATH cannot stand in for the verified wheel.
  • What it cannot establish. Another commit that differs only in its lock file can build the same wheel, and candidate runs are attested without approval, so only the release-tag ruleset keeps the tag on the published commit. The v0.1.0 tag was created after publication, and whether the ruleset was applied first was not recorded (release record).

Releasing describes what the attestation does and does not establish.

Scan your own agent

nexus-symbiote scan /path/to/agent --no-project-policy -o agentbom.json
nexus-symbiote validate agentbom.json --project /path/to/agent --no-project-policy
nexus-symbiote risk agentbom.json --project /path/to/agent --no-project-policy --threshold 50
  • --no-project-policy stops a .nexus-symbiote.yaml in a checkout that is not yours from choosing your gates.
  • With --project, validate and risk collect fresh evidence and fail when the BOM disagrees with it.
  • A scan that reaches one of the collection limits fails and names it. Raise it with --limit NAME=VALUE before the command, or with NEXUS_SYMBIOTE_LIMITS, and use the same limits for a later validate --project or risk --project, which collect the project again:
nexus-symbiote --limit entries=200000 --limit model-bytes=128GiB scan /path/to/agent --no-project-policy -o agentbom.json

The public-agent tutorial walks through a real repository end to end.

Gate drift in CI

Approve a baseline once, on the protected branch, then compare every pull request with it. baseline writes an unsigned file and diff checks no signature, so read it from a place the pull request cannot change, and take both snapshots the same way:

# One secret of 32 or more characters, the same for the baseline and every later scan.
export NEXUS_SYMBIOTE_SOURCE_BINDING_KEY="$AGENTBOM_BINDING_KEY"

# On the protected branch, through a reviewed pull request: approve the baseline.
mkdir -p .nexus-symbiote
nexus-symbiote baseline . --no-project-policy -o .nexus-symbiote/reviewed.json

# In the pull-request job (with fetch-depth: 0 or the base commit fetched): read the
# baseline from the base commit, never from the pull request.
BASE_SHA="${BASE_SHA:-origin/main}"
git show "$BASE_SHA:.nexus-symbiote/reviewed.json" > "$RUNNER_TEMP/reviewed.json"
nexus-symbiote scan . --no-project-policy -o "$RUNNER_TEMP/agentbom.json"
nexus-symbiote diff "$RUNNER_TEMP/reviewed.json" "$RUNNER_TEMP/agentbom.json" --fail-on-alert
  • What fails. --fail-on-alert fails on any change to the recorded inventory rated medium or higher, including a removed component and a coverage regression. An added source file that changes no component does not alert.
  • Same policy, same key. --no-project-policy keeps a policy file in the checkout from narrowing either snapshot. A different binding key, or a key on one side only, changes every binding and alerts.
  • Without the key, values pass. The inventory then records MCP launch arguments, environment values and endpoint paths only as redacted positions, so a served directory widened from ./notes to /, a changed environment value or a same-host endpoint path passes, and so does an A2A or RAG path changed on the same host that keeps its depth. Each such record carries a low completeness finding that names the key. A token or other value the redaction removes from .codex/config.toml, .cursor/cli.json or the Antigravity, xAI and Copilot settings passes too, and the IDE agent record carries the same finding. See source binding.
  • Paths are project-relative, so a baseline recorded in one checkout compares cleanly with a scan of another. Inventories written by 0.1.0 hold absolute paths: record the baseline again after upgrading.
  • Sign the reviewed baseline. To check that a reviewer you trust approved exactly that file, sign it with attest --purpose reviewed and run verify-attestation with the reviewer's public key, taken from outside the checkout, before diff; see gate a pull request on an attested baseline. An attestation does not show that the baseline is the newest one: bound its age with --max-age-days.

Run the comparison in a job the pull request cannot edit; see Protect the gate. The gate cannot alert on what the collectors do not record; the known limitations list what that is. Gate drift in CI explains where the baseline must come from and what each alert means.

GitHub Action

Pin the Action to a reviewed 40-character commit:

name: Agent inventory
on: [push, pull_request]
permissions:
  contents: read
jobs:
  inventory:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - uses: Hunzee-VC/Nexus-Symbiote@<reviewed-40-character-commit-sha>
        with:
          scan-path: .
          output-file: agentbom.json
          validate: 'true'
          risk-check: 'true'
          risk-threshold: '50'
          sarif-upload: 'false'

The Action installs its own hash-locked runtime, including Sigstore verification, in a private virtual environment that it keeps off the job's PATH, without changing the job's default Python. It ignores policy in the scanned checkout and fails on collection errors. Text from the checkout reaches the job log only inside a stop-commands block, neutralized, so it cannot set outputs or create annotations. Keep a job timeout like the one above: hostile input can keep a scan running until the job times out.

  • Every gate states its result. The result output is passed or failed, and gates is a JSON object with the state of each gate (collection, completeness, integrity, risk, drift): passed, failed, skipped (switched off by an input) or not-run. Every gate an input switches off is printed as a warning.
  • Integrity needs verified evidence. With validate: 'true', integrity passes only when at least one artifact hash or signature verifies and no component's evidence is unknown, so a repository in which nothing verifies, such as an empty checkout or one with only an MCP configuration, fails. A model counts only as a SafeTensors file whose every weight file is signed or digest-pinned in a signer policy you own, so a repository that contains a model fails unless every model meets such a policy. validate-integrity: 'false' checks completeness without integrity, for a repository whose models cannot be signed yet; validate: 'false' skips both.
  • The risk gate. risk-threshold: '0' removes only the aggregate score threshold; critical findings, unverified SafeTensors, incomplete coverage and missing evidence still fail it. With validate: 'true', risk reconciles the inventory against scan-path with the same signer policy, so a reconciliation failure fails it too, without a score. risk-check: 'false' skips it.
  • Unverified discovery evidence. A ShadowScan report in the repository is unsigned discovery evidence unless the signer policy verifies its producer, and the completeness and risk gates fail on it. allow-unsigned-discovery: 'true' accepts it explicitly, with a warning.
  • The drift gate. reviewed-baseline adds it: the absolute path of a reviewed AgentBOM outside the workspace, written from a trusted source like the signer policy below, which the Action compares with diff --fail-on-alert. The drift-passed and drift-alerts outputs report the result. The Action collects with the step's NEXUS_SYMBIOTE_SOURCE_BINDING_KEY, if any, so record the baseline with the same key, or with none.
  • Limits. limits raises collection limits, for example 'entries=200000,model-total-bytes=512GiB'. The job's own NEXUS_SYMBIOTE_LIMITS is ignored.
  • SARIF. Set sarif-upload: 'true' and add security-events: write to upload SARIF. The upload is skipped, with a warning, for an inventory that merges imported ShadowScan discovery. Give each Action step in one workflow run its own artifact-name and sarif-category.

Inputs and outputs are in action.yml. Write the signer policy outside the workspace from a trusted source, such as a repository or organization variable, never from the checked-out code:

      - name: Write the signer policy
        env:
          SIGNER_POLICY: ${{ vars.NEXUS_SYMBIOTE_SIGNER_POLICY }}
        run: printf '%s' "$SIGNER_POLICY" > "$RUNNER_TEMP/signers.json"
      - uses: Hunzee-VC/Nexus-Symbiote@<reviewed-40-character-commit-sha>
        with:
          scan-path: .
          signature-policy: ${{ runner.temp }}/signers.json

The Action refuses a policy inside the workspace, a path that passes through a symlink (including a symlinked RUNNER_TEMP), or a hard link, passes one snapshot of it to collection, validation and the risk gate, and reports its SHA-256, as sha256:<hex>, in the signature-policy-digest output. It verifies signatures offline against the Sigstore trust root shipped in its hash-locked sigstore package, never a cache on the runner.

Protect the gate

On pull_request, GitHub runs the workflow file of the pull request's merge commit, so the pull request under review can change these inputs, switch a gate off or delete the step unless workflow edits are restricted. Make the gate one the pull request cannot weaken:

  1. In a GitHub organization (recommended): require the workflow through an organization ruleset (Require workflows to pass before merging), which pins the gate to a reusable workflow file and ref outside the pull request's reach.
  2. In any repository: run on pull_request with read-only permissions, stage the baseline from github.event.pull_request.base.sha into $RUNNER_TEMP, require a mandatory CODEOWNERS review from security owners for .github/ and .nexus-symbiote/reviewed.json, and configure branch protection to require that check to pass:
name: Agent inventory gate
on: pull_request
permissions:
  contents: read
jobs:
  inventory:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - name: Stage the reviewed baseline from the base branch
        env:
          GH_TOKEN: ${{ github.token }}
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
        run: >-
          gh api -H 'Accept: application/vnd.github.raw'
          "repos/$GITHUB_REPOSITORY/contents/.nexus-symbiote/reviewed.json?ref=$BASE_SHA"
          > "$RUNNER_TEMP/reviewed.json"
      - uses: Hunzee-VC/Nexus-Symbiote@<reviewed-40-character-commit-sha>
        with:
          scan-path: .
          reviewed-baseline: ${{ runner.temp }}/reviewed.json

Avoid checking out github.event.pull_request.head.sha inside a pull_request_target workflow ("pwn request" hazard): pull_request_target runs in the base repository's security context, where adding any build, test, cache or secret step later turns an untrusted fork checkout into secret exfiltration or repository compromise. On pull_request, same-repository pull requests can supply NEXUS_SYMBIOTE_SOURCE_BINDING_KEY from repository secrets; fork pull requests receive no secrets, so collect fork baselines without the key or run keyed comparisons only on trusted branches.

Pre-commit hooks

The repository also ships pre-commit hooks (nexus-symbiote-scan and nexus-symbiote-risk), which apply the scan gate with the built-in policy and write nothing into the tree. pre-commit installs their dependencies from PyPI without hashes, so pin rev to a reviewed commit, and treat the hooks as a convenience for your own tree, not a control.

Policy and live mode

Every command is offline by default. Only discover-mcp, advisories and verify-live reach the network, and only in live mode (--live before the command, or NEXUS_SYMBIOTE_MODE=live); see offline and live modes.

A project policy, .nexus-symbiote.yaml, is read only from the project you scan or name with --project, never from a parent directory or from where an inventory file is saved. Generate one for a trusted project with nexus-symbiote init, or pass --no-project-policy when the checkout is not yours. The policy reference lists every setting.

Exit codes

Exit Meaning
0 The command completed and every requested gate passed
1 A gate or check failed (risk, integrity, drift, incomplete collection), an input could not be read, or an internal error occurred
2 Usage error, including an invalid collection limit, a binding key shorter than 32 characters, a malformed SOURCE_DATE_EPOCH, and a live command run in offline mode (advisories --dry-run contacts nothing and runs in either mode)

The CLI reference says when each command exits 1.

Collector reference

What each collector reads

Every path is relative to the project root. Each collector also bounds what it reads; see collection limits.

Collector Reads
mcp At the root: .mcp.json, mcp.json, claude_desktop_config.json, .claude/mcp.json, .cursor/mcp.json, .vscode/mcp.json, .gemini/settings.json, .roo/mcp.json, .kiro/settings/mcp.json, .amazonq/mcp.json, .windsurf/mcp.json, .cline/mcp.json and .codex/config.toml. At any depth: files named .mcp.json, mcp.json, mcp-config.json, mcp_config.json, mcp-servers.json, claude_desktop_config.json and cline_mcp_settings.json. The root resolver settings that apply to launchers: .npmrc, bunfig.toml, .yarnrc.yml, .yarnrc, .env.yarn, uv.toml and pyproject.toml. Python files, and TypeScript and JavaScript files (.ts, .mts, .cts, .tsx, .js, .mjs, .cjs and .jsx), that name an MCP SDK or, in Python, a supported agent framework, outside directories named tests, test, dist, build, docs, benchmarks or examples (which files)
model .safetensors, .gguf, .ggml, .onnx, Keras (.h5, .hdf5, .keras), .tflite, .mlmodel, .pte, saved_model.pb and pickle-family files (full list), with ONNX external data, SavedModel variables, shard indexes, configuration, tokenizer files, chat templates and declared custom code
prompt At the root: CLAUDE.md, .claude/CLAUDE.md, CLAUDE.local.md, AGENTS.md, AGENTS.override.md, .github/AGENTS.md, GEMINI.md, CODEX.md, codex.md, .codex/instructions.md, .github/copilot-instructions.md, .cursorrules, .clinerules (a file or a directory), .windsurfrules, system_prompt.md and system_prompt.txt. At any depth: CLAUDE.md, AGENTS.md, GEMINI.md and .cursorrules; another spelling of one of them, such as pkg/claude.md, is a collection failure unless a subagent, rule, prompt, skill or command directory inventories it. Every file under .claude/rules, .cursor/rules, .windsurf/rules, .roo/rules, .kiro/steering, .github/instructions, .github/prompts, .claude/output-styles, .claude/agents and .github/agents, and under the skill and command directories .claude/skills, .claude/commands, skills, .agents/skills, .github/skills, .cursor/skills, .gemini/skills, .cursor/commands and .gemini/commands, including a skill's bundled scripts. Files that the root CLAUDE.md, .claude/CLAUDE.md, CLAUDE.local.md and GEMINI.md, and every nested CLAUDE.md and GEMINI.md, import with @path, to five hops
rag At any depth: rag_config.json, rag_config.yaml, rag_config.yml, retrieval_config.json, vector_store_config.json, kb_config.json and knowledge_base.json. At the root: .env, .env.local and .env.example, for key names only. Python files, for vector-store imports
registry At the root only: package.json with one lockfile (npm-shrinkwrap.json or package-lock.json, pnpm-lock.yaml or yarn.lock); requirements*.txt; pyproject.toml, with uv.lock or poetry.lock; Dockerfile, Dockerfile.agent and Containerfile; and the Compose files and override files Compose reads by default. For inventoried packages, also the registry and index settings of the root .npmrc, .yarnrc, .yarnrc.yml, bunfig.toml, pip.conf, pip.ini and uv.toml, and those in requirements files and pyproject.toml (registry and index settings). What it reads gives the fields
a2a agent.json and agent-card.json in any .well-known directory and in the root, and a2a/agent.json. At the root, client configuration in a2a.json, a2a.yaml, a2a.yml or a2a_config.json. Python files outside directories named tests, test, dist, build, docs, benchmarks or examples, for SDK references
ide_agents Marker files and settings of Claude Code (also .claude settings in subdirectories, and plugin manifests), Codex (.codex/config.toml), Cursor (including .cursor/hooks.json and .cursor/cli.json), Gemini CLI (.gemini/settings.json), Windsurf, Cline, Copilot (.github/copilot-extensions.json), Antigravity and xAI; .vscode/extensions.json, for recommended AI extensions
shadowscan A report at .shadowscan/report.json, shadowscan-report.json or shadowscan.json; reports at more than one of them are a failure

Coding-agent settings

For Claude Code, ide_agents records the permission mode (bypassPermissions is a high finding, acceptEdits medium), blanket grants in its allow list, how many allow, ask and deny rules and hook handlers it declares, and findings for hook commands, apiKeyHelper, MCP auto-approval, plugins and additional directories. It does not record the rules or hook commands themselves. For Codex it records the approval and sandbox settings, for Cursor its hook commands, and for Cursor, Windsurf, Cline and Codex an agent.permissionMode key in their JSON settings. The MCP servers that Codex and Gemini CLI settings declare are inventoried by the mcp collector. Every settings file ide_agents recognizes is bound by a digest, so a changed value is a high diff alert; for Codex config.toml, .cursor/cli.json and the Antigravity, xAI and Copilot files, a change only to a value the redaction removes, such as a token, an MCP environment value or a URL path on the same host, alerts only with NEXUS_SYMBIOTE_SOURCE_BINDING_KEY, and the record then carries a low completeness finding, while a host glued to such a URL after a shell operator or another URL scheme still alerts; without the key, a URL whose host URL parsers read differently, such as one with a backslash in its authority, is a collection failure. Instructions and IDE settings lists what this leaves out.

Registry gaps in requirements files and Dockerfiles

A gap makes the collector partial, which fails scan, validate and risk by default. The registry collector page lists every other gap and finding.

  • Requirements files are read by pip 26.3's rules, without following includes, constraints files, editable installs or URL, VCS and path requirements. These are gaps: an include, constraints file or editable install that reaches beyond the root requirements*.txt files or the project itself; a URL, VCS, archive or path requirement without a package name, other than the project itself; an option pip 26.3 does not know, on an option line; and a ${NAME} variable in a requirement's name or in an option line's option place. A declared encoding other than UTF-8 is a failure.
  • Recorded without a gap: a name @ URL requirement, under its name and URL origin, or as written when the URL has no //, unless pip could read its text as a path in the project. Options after a requirement on the same line are not checked, except -e and --hash; --hash values are recorded. A variable inside an option's value is neither resolved nor reported.
  • Dockerfiles are read as BuildKit reads them: the instruction word in any case, --flag words skipped, continued lines joined and trimmed, comment lines and heredoc bodies skipped, and FROM lines that name an earlier build stage left out. These are gaps: an ARG substitution, quotes or escapes in the image, a FROM line or heredoc that cannot be parsed, and an escape directive other than a backslash.

Documentation

The documentation hub organizes every guide by Why, What and How. Start here:

If you want to… Read
See how the tool is put together and where untrusted input enters Architecture
Know what the tool protects, from whom, and which control answers each threat Threat model
Understand the trust model Security model
Know what is not recorded, compared or verified Known limitations
Run the full offline demo Examples walkthrough
Scan a real project end to end Scanning a public agent
Look up a command, option or exit code CLI reference
Write a project policy or suppress a finding Policy reference
Raise a collection limit Collection limits
Keep a drift baseline out of a pull request's reach Gate drift in CI
Sign a reviewed baseline and verify it before diff Inventory attestation
Make two collections of one project compare equal, or byte-identical Reproducible inventories
Compare what an agent's traces report with its inventory Trace evidence
Trace the recorded paths from agents to risky components Graph view
Map exports to CycloneDX, SPDX, SARIF and VEX fields Standard exports
Check which G7 AI SBOM minimum elements an export carries G7 AI SBOM minimum elements
Relate findings to OWASP, MITRE ATLAS, NIST AI RMF, EU AI Act, ISO/IEC 42001 and CWE Framework crosswalk
Import Nexus-ShadowScan discovery ShadowScan integration
Approve the reviewed agent in ShadowScan's reconciliation Approve agents for ShadowScan
Mediate tool calls with signed contracts Runtime assurance · Runtime contract reference
Release or verify a release Releasing · Repository controls

Project status

Nexus SymBiote is alpha with a single maintainer: APIs, formats and policy semantics may change, and there is no SLA. Known detection gaps are listed in the known limitations. CI, including CodeQL, runs on every push to main and every pull request that targets it. It tests the hash-locked dependency set on Python 3.11 to 3.14 and the declared dependency floors on Python 3.11; a weekly workflow tests the newest releases the declared ranges admit. A release is published only by the environment-gated release workflow, after its preflight has confirmed branch protection and a successful Verified CI run for the exact commit. Planned work is tracked in the roadmap and changes are in the changelog.

Nexus-ShadowScan is the companion discovery engine — it finds agents across many surfaces, and Nexus SymBiote turns its reports into AgentBOMs with local artifact and supply-chain verification.

Security

Report vulnerabilities privately as described in SECURITY.md, never in a public issue.

Contributing

Contributions are welcome — especially tested collector coverage, regression fixtures and documentation. See CONTRIBUTING.md for setup and review contract, GOVERNANCE.md for decisions, and SUPPORT.md for troubleshooting. Please follow the code of conduct.

Citation

If you use Nexus SymBiote in research or tooling, cite it with the metadata in CITATION.cff, or use Cite this repository on GitHub.

License

MIT. Copyright (c) 2026 Emmanuel Dahunsi (AISecNomad). The vendored schemas under src/nexus_symbiote/generators/schemas/vendor/ keep their upstream licenses: the CycloneDX schemas are Apache-2.0 (license), and the SPDX 3.0.1 schema and context come from the SPDX Specification, which is Community-Spec-1.0 with CC-BY-3.0 for pre-existing portions (license). The notice gives their sources, so the distribution's license expression is MIT AND Apache-2.0 AND Community-Spec-1.0 AND CC-BY-3.0.

About

Agent Bill of Materials (AgentBOM) covering Agentic AI Supply Chain (Data, Model, Tool, Registries, Skills, A2A/MCP Servers) Supports and Extends Cyclone 1.7 DX and SPDX 3.0

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages