Repository navigation
Consolidated AgentBOM roadmap: reproducible and attested inventories, trace-diff, crosswalk, G7 coverage, graph view; close verified gate bypasses - #138
Merged
Conversation
core/content_digest.py defines the profile nexus-symbiote/inventory-content/v1: the RFC 8785 digest of a native inventory without its serial number and collection times (metadata.serial_number, metadata.timestamp, metadata.completeness.timestamp and each claim's observed_at), each removed only when it has the shape the producer writes. Two collections of the same content digest equally wherever and whenever they ran. The digest command, reproducible scans, attestation and the ShadowScan export build on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
`SOURCE_DATE_EPOCH` now pins every collection-time field written by `scan`, `baseline` and `import-shadowscan`. The serial number becomes the version 5 UUID of the inventory content digest, so two scans of the same tree produce byte-identical native JSON, CycloneDX and SPDX. The risk gate clock is deliberately not pinned: a backdated commit must not keep an expired suppression alive. `digest INVENTORY` prints the file SHA-256, the canonical (runtime) digest and the content digest under the profile `nexus-symbiote/inventory-content/v1`, each computed independently; a digest that is undefined for a valid inventory is reported as unavailable with its reason and exits 1. `AIBOM.load` is split into `read_native_bytes`, `parse_native_bytes` and `AIBOM.from_native` with identical behaviour, so callers can validate the bytes they digest. Deep nesting in the content digest raises a documented `ValueError` instead of `RecursionError`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
`attest` signs an in-toto Statement v1 in a DSSE envelope with Ed25519 over an inventory file: its SHA-256, and for native inventories the content digest, spec version and component count, the purpose, an optional source revision and the signing time. `verify-attestation` checks the envelope against operator-held trusted keys with a threshold, the purpose, the maximum age and either the exact bytes or, with `--match content`, the inventory content digest of a fresh scan. The module reuses the runtime DSSE primitives and keeps its own predicate type, so a runtime trust envelope and an inventory attestation cannot be substituted for each other. Trusted keys inside a git working tree, keys reached through links, hard-linked keys and duplicated keys are refused. Native classification is bounded at 256 nesting levels so it is a function of the bytes, not of the caller's stack. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
…and models - Digest every IDE settings file the ide_agents collector recognizes, including `.cursor/cli.json`, the Antigravity, xAI and Copilot files and all of `.codex/config.toml`, so a widened Cursor CLI allow list or a new Codex `notify` command is a high `diff` alert. The newly bound files are digested after redaction, so an unkeyed inventory cannot confirm a guessed token or MCP environment value; the keyed HMAC still covers every value as read. - Report the unassigned default-ignorable code points U+2065 and U+FFF0-U+FFF8 as high `invisible_characters` and U+E0080-U+E00FF and U+E01F0-U+E0FFF as critical `tag_characters` on every prompt surface, and neutralize them in terminal output. - Check Hugging Face chat templates for the sandbox-escape shapes GGUF templates are checked for. A match is a critical `serialization_format` finding; only a model that would be rated `safe` is re-rated, so the invalid-SafeTensors gate cannot be hidden by a template suppression. - Report default-excluded names (`.git`, `node_modules`, `__pycache__`, `.venv`, `venv`) inside agent directories as coverage gaps. - Collect nested `GEMINI.md` and `.cursorrules` and follow imports of nested `GEMINI.md`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
`graph INVENTORY` renders the inventory as a static HTML page or a versioned JSON document (`nexus-symbiote/graph-view/v1`): components, dependency and reference edges, coverage, and the shortest exposure paths from an agent to a component with a risk class. `--focus` selects a neighbourhood and `--max-nodes` bounds the view (default 2,000, at most 20,000); a larger view is refused rather than truncated silently. Risk classes (network-egress, code-exec, write-capable, credential-access, unpinned, unsigned and others) are derived deterministically from what the inventory records, each with its basis; nothing is written back to the inventory. A loopback endpoint is recognized only from a plain http(s) or ws(s) authority, so an ambiguous authority errs toward network-egress. The page carries a Content-Security-Policy that pins its one style block and one script by SHA-256, has no links, URLs, event handlers or style attributes, and passes every inventory string through neutralization, clipping and HTML escaping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
A versioned catalog maps every documented finding category, plus `runtime_undeclared`, to entries of eight framework editions: OWASP LLM Top 10 2025 and 2026, OWASP Agentic (ASI) 2026, MITRE ATLAS 2026.10, NIST AI RMF 1.0, the EU AI Act, ISO/IEC 42001:2023 and CWE. Each reference carries a one-line rationale and an optional severity scope; references that do not hold for every kind of finding in a category say which ones they do not hold for. Every category now has a SARIF rule. Rule tags are derived from the catalog: existing CWE, OWASP LLM 2025 and Agentic tags keep their order, and OWASP LLM 2026, ATLAS and NIST AI RMF tags are appended. The `crosswalk` command reports an inventory's findings per framework entry as JSON (`nexus-symbiote/crosswalk/v1`) or escaped Markdown. A crosswalk is a reading aid for reviewers, not a compliance verdict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
…rted
JavaScript-family MCP servers (`.js`, `.mjs`, `.cjs`, `.jsx`, `.tsx`) are
analysed like TypeScript ones, including namespace imports and default
imports of the server module. Forms the analysis cannot follow are now
named coverage gaps instead of silent skips, so the default gate fails:
- the SDK loaded by `require()` or `import()`, or through a specifier
with an `npm:`, `jsr:` or URL prefix, a version or a path;
- a `require()` or `import()` whose module name cannot be read;
- a Python MCP server module loaded dynamically (`importlib`,
`__import__`, `pkgutil.resolve_name`, literal relative names);
- an MCP server launched from application code (`StdioServerParameters`,
`StdioClientTransport`).
Type-only `import("...")` positions are erased before analysis, and
SDK-use tracking stays linear in the file size. Aliases resolved outside
the file (Node `#` subpath imports, tsconfig `paths`, import maps) remain
documented limits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
`trace-diff INVENTORY TRACE...` reads OpenTelemetry traces the operator exported as OTLP/JSON (one ExportTraceServiceRequest or Collector JSON Lines) and reconciles the agents, models, tools and MCP endpoints that spans with GenAI or MCP semantic-convention attributes report against what the inventory declares. Undeclared use is a high `runtime_undeclared` finding and exits 1 (`--fail-on none` reports without failing); declared items no span reported are listed as unobserved, and ambiguous or unreadable identifiers are reported, never guessed. The reader is bounded (64 MiB per file, 1,000,000 spans, 100,000 distinct identifiers) and allowlisted: content attributes such as `gen_ai.input.messages`, tool arguments and results, retrieval documents and memory records are recognized by key and never read, so no prompt or completion text reaches any output. A span counts as GenAI or MCP only through its own attributes, and a present but unreadable operation name, method or transport fails the gate. Telemetry is recorded as `reported` evidence: it does not raise a component's assurance. The evidence document (`nexus-symbiote/trace-evidence/v1`) and optional SARIF are deterministic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
…list `export-inventory INVENTORY --format shadowscan-simple --resource-prefix PREFIX -o FILE.json` writes the reviewed repository agent as ShadowScan's documented simple inventory list, so the agent approved in SymBiote review becomes ShadowScan's approved inventory. The entry binds exactly the repository's root project on the `code` surface through one escaped literal resource pattern and the `project` discriminator; checked with ShadowScan's own loader and matcher, a coding-agent configuration, a credential file, a nested project, a lookalike repository, another surface and case or whitespace variants stay unapproved. The command runs offline, writes deterministic bytes, prints the inventory's file SHA-256 and content digest on standard error, and refuses with fixed text an inventory without exactly one repository agent, an undeclared identity unless `--include-unnamed` is given, and names or owners that are empty, padded, oversized or contain control or format characters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
`minimum-elements INVENTORY` evaluates an inventory, and the CycloneDX export generated from it, against the 50 elements of the G7 "Software Bill of Materials for AI - Minimum Elements" (12 May 2026), in its seven clusters and with the element names verbatim. Each element is present (with its basis, declared or observed), missing, explicitly unknown, not applicable, or needs producer input that no repository scan can supply, with the fields to fill. Coverage is reported with its denominator, `k of n assessable`; it is never a compliance verdict, and the document itself is non-binding and assigns no identifiers. A present SBOM dependency relationship is marked partial when some relationships stay in the native extension, with the count exported. Text with no visible character counts as missing. Output is text, JSON or escaped Markdown; `--fail-under PERCENT` gates on coverage with a strict decimal parser, so `nan` cannot open the gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
README, CHANGELOG, SECURITY.md, CONTRIBUTING.md, CLAUDE.md, the docs hub, the glossary, the known limitations, the threat and security models, the architecture page and the CLI reference now describe the new commands (digest, attest, verify-attestation, trace-diff, crosswalk, minimum-elements, graph, export-inventory) and the closed gate bypasses, and no longer say that inventories cannot be signed or that now-gapped forms pass silently. The CHANGELOG marks as Breaking what can turn a passing CI job red: a malformed SOURCE_DATE_EPOCH, the new coverage gaps, and baselines recorded by earlier builds alerting on the newly bound IDE settings files (checked with a real baseline from main). ROADMAP.md's Delivered, Acceptance and Scorecard sections were checked against the code and corrected where they overstated what landed. Every statement was reviewed for truth against the code and for consistency across the documentation; README links stay absolute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
- The Python MCP source analysis bound is an operator limit, `python-nodes` (default sized from measurement), set only with `--limit` or `NEXUS_SYMBIOTE_LIMITS`; a module above it is a collection failure that names the limit. The repository's own scanners/registry.py no longer trips it, and the helper analysis and the MCP origin-label scan are linear. - A case variant of an instruction file the prompt collector reads at any depth, such as `pkg/claude.md` or `a/b/Agents.md`, is a collection failure like a root variant, also beside the exact name; inventoried spellings are not reported, and their imports are followed. - The registry collector records registry and index redirects for inventoried packages (`.npmrc` default and scoped registries and npm's `scope`, `.yarnrc`, `.yarnrc.yml`, `bunfig.toml`, `--index-url`, `--extra-index-url`, `--find-links` and `-i` in requirements files, project `pip.conf`/`pip.ini` including `pypi-url`, uv and Poetry and PDM sources): origin only, credentials withheld, the package URL identity withheld, and a low `registry_origin` finding. An unreadable redirect file is a failure and an ambiguous one a coverage gap. - The Action's `scanners` input says that naming collectors makes coverage partial, which fails the default `validate` and `risk` gates. Symbolic links that no collector would read stay collection failures: a link's name cannot show whether it names a directory, and finding out would mean examining a path outside the project. The known limitations record why. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
Main's deterministic export (ARCH-F1) and this branch's reproducible inventories now share one serial scheme: prepare() derives a missing serial as the version 5 UUID of the inventory content digest (profile nexus-symbiote/inventory-content/v1), which also leaves out an imported report's source_report_path. The serial is the same with or without SOURCE_DATE_EPOCH and can be recomputed from the native file. Without SOURCE_DATE_EPOCH an inventory records no collection time, so native JSON and CycloneDX are byte-identical by default; with it, the instant is recorded and SPDX is byte-identical too. The G7 report counts the SBOM timestamp as missing unless the variable is set (demo: 11 of 39). The roadmap, CHANGELOG and SECURITY.md reflect #131 and #132, and the roadmap records the audit residuals as delivered (D10). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
Four parallel reviews (injection, confinement, attestation and telemetry, semantics and bypass hunting, regressions and documentation) reported findings that two independent skeptics each reproduced; fixed with regression tests that fail on the previous code, each fix checked again by an independent reviewer: - Output: every Default_Ignorable_Code_Point is neutralized, so variation-selector payloads cannot hide in graph, crosswalk, G7, trace or attestation output. - IDE settings: the unkeyed redacted digest binds every host and command (cut at shell operators and before each URL scheme); an ambiguous URL authority fails the scan; such records carry the low completeness finding that names the binding key. - Prompt: resources that are not strict UTF-8 are checked as an agent decodes them; binary files are checked for runs and dense plane-14 text. - Models: a symlinked additional_chat_templates directory is read through the link, case variants of model and template files are failures, and the escape check reads templates as Jinja does. - Python MCP sources: launchers and server classes from any module other than their own recognized one are gaps; any call with a literal naming an MCP module in any argument position is a gap (logging.getLogger and owned server classes excepted). - trace-diff: a reported address is matched only with tools at that address; a local call never matches a remote-only server; an attribute list over the bound fails the gate; the SARIF rule has its own help. - Crosswalk rationales no longer overstate; the inventory-export coverage floor is restored; docs corrected (G7 gate line, case counts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
Parenthesize every multi-line string element of a list, so it cannot be read as a missing comma, and pass _references to its subject rule directly instead of through a lambda. Output is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
Both import-lookups (main) and python-nodes (this branch) are operator limits. Versionless inventories are read alike by every reader, including the content digest, digest, attest, trace-diff and export-inventory. A lone surrogate read from the checkout still stops the export, because the serial is derived from the content digest; the test now pins that fail-closed result. Suppression fixtures carry the fingerprints #133 requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
core.content_digest imports core.aibom only inside the function that needs it, since core.aibom imports content_digest to derive serials, and graph_cli loads inventories with its own reader instead of importing the cli module back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
core.aibom derives serials from core.content_digest, so content_digest must not import core.aibom even inside a function. inventory_digests, the one helper that validates through AIBOM, now lives in core.inventory_digests, and core.aibom imports content_digest at module level. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYWyP3wni7ByCUDqQ1WqBd
trace-diff read a tool only from spans whose gen_ai.operation.name was exactly execute_tool or whose mcp.method.name was exactly tools/call, and an agent only from create_agent and invoke_agent. A span that named a tool under a missing, custom or differently cased operation name was neither observed nor counted as unclassified, so the default gate passed on a trace that reported an undeclared tool. A span that names a tool is now a call to it whatever it calls its operation. A span whose operation name is outside the convention and that names an agent is an agent operation; spans of other known operations may carry the name of the agent they run in and stay as they were. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EESHz3nNgsvwVBKtotKHB
aisecnomad
commented
Nov 12, 2026
aisecnomad
left a comment
Collaborator
Author
There was a problem hiding this comment.
Security & Engineering Verification Summary (Approved for Merge)
- CI & Provenance Gates: All 19/19 GitHub Actions checks (
Verified CI,CodeQL,Tests Python 3.11–3.14,Coverage Python 3.12,Composite Action end to end,Committed security regression contracts) passed onc5f97be049(0commits behindmain). - Local Static & Security Verification:
ruff check,ruff format --check(307 files),mypy(106 source files, 0 issues),check_workflows.py,check_locks.py,check_repository_hygiene.py(474 tracked paths), andcheck_security_regressions.py(22 cases, 0 failures) all passed. - Subsystem Security Audit:
- Attestation & Content Digest (
attestation.py,content_digest.py): DSSEv1 PAE verified prior to JSON parsing; strict I-JSON + RFC 8785 JCS canonicalization;match='bytes'recomputescontent_digestfrom verified native bytes;--trusted-keyenforcesst_nlink == 1and working-tree exclusion by(st_dev, st_ino). - HTML/SVG Graph View (
graph_view.py): Strict CSP (default-src 'none'with SHA-256 style/script hashes),neutralize()+html.escape(..., quote=True)across all untrusted inventory strings, and zero DOM HTML/eval sinks. - OTLP Trace Evidence & Scanners (
otlp.py,trace_evidence.py,ide_agents.py,mcp.py,model.py,prompt.py,registry.py,ts_tools.py): Explicit byte/span/attribute/node bounds, linear-time$O(N)$resolvers, redacted IDE config digests, and Unicode 15.1 default-ignorable neutralization.
- Attestation & Content Digest (
- Follow-ups Tracked:
- Resolving the 2 false-positive
crosswalk.py:260(FRAMEWORK_KEYS) threads (crosswalk_cli.pyand test suites importFRAMEWORK_KEYS;__all__export +trace_evidence.py:_endpointURL authority guard + macOS APFS case-folding confinement in PR #140 follow immediately).
- Resolving the 2 false-positive
aisecnomad
marked this pull request as ready for review
November 12, 2026 19:47
aisecnomad
pushed a commit
that referenced
this pull request
Nov 12, 2026
to #138 The branch was based on the pre-squash history of #130. Its change is re-applied on current main; conflicts with later fixes on main are resolved in favour of main where main already covers the same problem. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EESHz3nNgsvwVBKtotKHB
aisecnomad
pushed a commit
that referenced
this pull request
Nov 12, 2026
…with #131 to #138 The branch was based on the pre-squash history of #130. Its change is re-applied on current main; conflicts with later fixes on main are resolved in favour of main where main already covers the same problem. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EESHz3nNgsvwVBKtotKHB
aisecnomad
pushed a commit
that referenced
this pull request
Nov 12, 2026
…ith #131 to #138 The branch was based on the pre-squash history of #130. Its change is re-applied on current main; conflicts with later fixes on main are resolved in favour of main where main already covers the same problem. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EESHz3nNgsvwVBKtotKHB
This was referenced Nov 12, 2026
aisecnomad
added a commit
that referenced
this pull request
Nov 12, 2026
…oot, DSSE quorum & thread-safe budgets (#139) Runtime authorization canonicalizes arguments by default, a quorum counts only unrevoked signers, and the state store is checked against its identifier. Collection limits are held per context, key generation writes without following links, and the schema worker accepts only integral floats where an integer is required. Reconciled with main after #131 to #138: the embedded Sigstore root change is superseded by #137 and was dropped. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EESHz3nNgsvwVBKtotKHB
aisecnomad
pushed a commit
that referenced
this pull request
Nov 13, 2026
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EESHz3nNgsvwVBKtotKHB
aisecnomad
pushed a commit
that referenced
this pull request
Nov 13, 2026
…142 The page was written before those merged. The gate guidance now matches the README (a ruleset or code-owner review, not a pull_request_target job), inventory attestation is described as implemented with only the gate and Action integration outstanding, and the statement that the tools refuse every operator-owned file inside the project is narrowed to what they refuse. Adds the changelog entry. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EESHz3nNgsvwVBKtotKHB
aisecnomad
added a commit
that referenced
this pull request
Nov 13, 2026
Adds docs/operational-guidance.md, linked from the documentation index: the operator and CI practices the documented guarantees depend on, the continuity risk of a single maintainer, and what is not yet implemented. Reviewed against main after #138 to #142: the gate guidance matches the README, attestation is described as implemented with only the gate and Action integration outstanding, and the refusal of operator-owned inputs is stated as the commands enforce it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015EESHz3nNgsvwVBKtotKHB
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Seven external reviews and the v0.1.0 audit (347 findings) made overlapping, sometimes conflicting recommendations for making Nexus SymBiote the reference AgentBOM tool. This change checks every recommendation against
main(3ad53a2) and against primary sources, consolidates them into one roadmap (ROADMAP.md), and implements its "Delivered" set. Each commit is one self-contained unit and can be reviewed on its own.Closes verified gate bypasses (each reproduced at
mainfirst; each now fails a default gate):.cursor/cli.jsonShell(ls)→Shell(*), a Codexnotifycommand, the Antigravity, xAI and Copilot files) are now digested, after redaction so the digest cannot confirm a guessed secret..git,node_modules,__pycache__,.venvandvenvinside agent directories are coverage gaps; nestedGEMINI.mdand.cursorrulesare collected.require,import(),npm:/jsr:/URL/path specifiers, non-literal names, Python dynamic imports, servers launched from application code) are named gaps instead of silent skips.python-nodes; nested case variants of instruction files are failures; registry and index redirects for inventoried packages (.npmrcincludingscope,.yarnrc*,bunfig.toml, requirements index options, projectpip.conf/pip.ini, uv, Poetry and PDM sources) are recorded with credentials withheld and a lowregistry_originfinding; the Action'sscannersinput says naming collectors makes coverage partial.trace-diffnever matches a call to a tool served at another address and fails on attribute lists over its bound.Adds evidence features (all offline):
digestandSOURCE_DATE_EPOCH: byte-identical native JSON, CycloneDX and SPDX for identical inputs, and a content digest (nexus-symbiote/inventory-content/v1) that is equal for equal content.attest/verify-attestation: DSSE + in-toto Statement v1 with Ed25519, threshold, purpose, age and byte or content matching; trusted keys inside the checkout are refused.trace-diff: OpenTelemetry GenAI/MCP traces (OTLP/JSON) reconciled with the inventory; undeclared runtime use is a highruntime_undeclaredfinding; content attributes are never read; telemetry is recorded asreported, never as verification.crosswalkreports findings per framework entry (a reading aid, not a compliance verdict).minimum-elements: coverage of the 50 G7 AI SBOM minimum elements with its denominator; never a compliance verdict.graph: a self-contained, CSP-hashed HTML view (or JSON) with derived risk classes and shortest exposure paths; unclassified means unknown, not safe.export-inventory --format shadowscan-simple: the reviewed repository agent as ShadowScan's approved inventory, checked against ShadowScan 0.1.4's own loader and matcher.Merged with current
main(#131, #132): main's deterministic export (ARCH-F1) and this branch's reproducible inventories share one serial, the UUIDv5 of the inventory content digest (profilenexus-symbiote/inventory-content/v1). WithoutSOURCE_DATE_EPOCHan inventory records no collection time, so native JSON and CycloneDX are byte-identical by default; with it, SPDX is too and the G7 "SBOM timestamp" element is present.Validation (on the pushed tree, Python 3.13):
make check(ruff, format, mypy strict, workflow, hygiene and lock checks, the full suite with coverage floors, the security regression corpus with 22 cases, build andtwine check) andmake wheel-smoke: 8,269 passed and 2 expected case-sensitive-file-system skips, package coverage 95.17% statements and 91.16% branches with every floor met, 22 security regression cases with 0 failures, and both distributions passtwine check. Every bypass fix has a regression test shown to fail on the previous code. The documented demo is unchanged (11 components, score 37). End to end onexamples/demo-agent: two scans at different paths are byte-identical and share the content digest;attest/verify-attestationaccept content matching and refuse a wrong purpose; the graph page renders under its CSP with no console errors in headless Chromium.Related issues
None.
Type of change
Breaking: new coverage gaps and bound settings files can fail a default gate that passed before (see CHANGELOG Changed); a malformed
SOURCE_DATE_EPOCHnow exits 2; SARIF rules exist for every category, so code scanning shows new rule ids for categories that used the generic rule.Checklist
make checkruns the offline subset of the required CI checks. See CONTRIBUTING.md.ruff check src tests scriptspassesruff format --check src tests scriptspassesmypy src/nexus_symbiote scriptspassesmake workflow-check hygiene lock-check)make lock, and the "Dependency health" run is green (no dependency changes)Review record
Written with AI assistance (Claude Code), in one session. Every unit was implemented by one agent, reviewed adversarially by a separate agent that did not write it, and fixed with a regression test per confirmed finding (each regression test was shown to fail against the pre-fix code). The integrated change then went through a multi-dimension adversarial review in which each finding needed two independent skeptics to confirm it before it was fixed. All of these ran inside the same session, so none of them is an independent human review. No human has reviewed the rendered diff yet: the maintainer's review after Verified CI finishes is still required.
Security-sensitive changes
docs/known-limitations.mdandSECURITY.mdupdated)main)scripts/benchmark_security.py; four new hidden-character cases)tests/test_modes.py)Limitations
.cursor/settings.json,.codex/config.json) keep an unkeyed digest of the file as read, so a holder of the inventory can confirm a guess of a value in them, including Gemini MCP environment values; moving them to the redacted digest changes every baseline once. The release workflow exportsSOURCE_DATE_EPOCH, so the release evidence now records the commit time.getattr/globals()/code in strings, and SDK aliases resolved outside the file (Node#imports, tsconfigpaths, import maps) leave no gap; the new literal-argument rule can report a gap for an unrelated call that names an MCP module. All documented indocs/known-limitations.md.trace-diffevidence isreported: the agent's own instrumentation can omit or forge spans; a missing span proves nothing. A one-object 64 MiB trace can take about 2 GB of memory.crosswalkandminimum-elementsare reading aids and coverage reports, never compliance verdicts; NIST AI RMF, EU AI Act, ISO/IEC 42001 and CWE identifiers were checked against secondary sources.rich; no lock was changed.Generated by Claude Code