Know what your AI agents are built from. Gate them on evidence. Catch drift before it ships.
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 driftAlpha. 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
diffcan alert on.
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 --versionReplace 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).
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-policyThe 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.jsonvalidate 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.
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.
An AgentBOM: one evidence-backed inventory of an agent, with gates, drift checks and standard exports.
┌──────────────────────────────────────────────┐
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
- Collect. Each collector reads its surfaces through confined, size-bounded file reads and returns components, findings and coverage status.
- Assemble. Results merge into one AgentBOM. Every claim keeps its assurance level: declared, observed or verified.
- Gate.
validatechecks completeness and integrity;riskscores findings against your policy. Gaps the collectors report fail closed; surfaces they do not support are not reported. - Compare.
diffcompares two snapshots semantically and rates each change. - Export. Standard formats are generated from the same collection, so every export describes the same evidence.
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.
| 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.
| 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.
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.
Install a verified release, inventory your agent, then let CI hold the line.
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
.venvand stops at the first failing command, such as a missing or signed-outghor a missing tag. Placing it before&&or||makes bash ignore itsset -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 yourPATHcannot 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.0tag 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.
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-policystops a.nexus-symbiote.yamlin a checkout that is not yours from choosing your gates.- With
--project,validateandriskcollect 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=VALUEbefore the command, or withNEXUS_SYMBIOTE_LIMITS, and use the same limits for a latervalidate --projectorrisk --project, which collect the project again:
nexus-symbiote --limit entries=200000 --limit model-bytes=128GiB scan /path/to/agent --no-project-policy -o agentbom.jsonThe public-agent tutorial walks through a real repository end to end.
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-alertfails 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-policykeeps 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
./notesto/, 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 lowcompletenessfinding that names the key. A token or other value the redaction removes from.codex/config.toml,.cursor/cli.jsonor 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 reviewedand runverify-attestationwith the reviewer's public key, taken from outside the checkout, beforediff; 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.
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
resultoutput ispassedorfailed, andgatesis a JSON object with the state of each gate (collection,completeness,integrity,risk,drift):passed,failed,skipped(switched off by an input) ornot-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. Withvalidate: 'true', risk reconciles the inventory againstscan-pathwith 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-baselineadds 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 withdiff --fail-on-alert. Thedrift-passedanddrift-alertsoutputs report the result. The Action collects with the step'sNEXUS_SYMBIOTE_SOURCE_BINDING_KEY, if any, so record the baseline with the same key, or with none. - Limits.
limitsraises collection limits, for example'entries=200000,model-total-bytes=512GiB'. The job's ownNEXUS_SYMBIOTE_LIMITSis ignored. - SARIF. Set
sarif-upload: 'true'and addsecurity-events: writeto 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 ownartifact-nameandsarif-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.jsonThe 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.
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:
- 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.
- In any repository: run on
pull_requestwith read-only permissions, stage the baseline fromgithub.event.pull_request.base.shainto$RUNNER_TEMP, require a mandatoryCODEOWNERSreview 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.jsonAvoid 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.
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.
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 | 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.
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 |
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.
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*.txtfiles 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 @ URLrequirement, 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-eand--hash;--hashvalues 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,
--flagwords skipped, continued lines joined and trimmed, comment lines and heredoc bodies skipped, andFROMlines that name an earlier build stage left out. These are gaps: anARGsubstitution, quotes or escapes in the image, aFROMline or heredoc that cannot be parsed, and an escape directive other than a backslash.
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 |
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.
Report vulnerabilities privately as described in SECURITY.md, never in a public issue.
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.
If you use Nexus SymBiote in research or tooling, cite it with the metadata in CITATION.cff, or use Cite this repository on GitHub.
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.
