Skip to content

Consolidated AgentBOM roadmap: reproducible and attested inventories, trace-diff, crosswalk, G7 coverage, graph view; close verified gate bypasses - #138

Merged
aisecnomad merged 21 commits into
mainfrom
claude/agentbom-evidence-roadmap
Nov 12, 2026
Merged

aisecnomad merged 21 commits into
mainfrom
claude/agentbom-evidence-roadmap

Conversation

@aisecnomad

Copy link
Copy Markdown
Collaborator

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 main first; each now fails a default gate):

  • IDE settings that were recognized but not bound (.cursor/cli.json Shell(ls) → Shell(*), a Codex notify command, the Antigravity, xAI and Copilot files) are now digested, after redaction so the digest cannot confirm a guessed secret.
  • Unassigned default-ignorable code points (U+2065, U+FFF0–U+FFF8, U+E0080–U+E00FF, U+E01F0–U+E0FFF) are hidden-character findings and are neutralized in output.
  • Hugging Face chat templates get the sandbox-escape check GGUF templates get.
  • .git, node_modules, __pycache__, .venv and venv inside agent directories are coverage gaps; nested GEMINI.md and .cursorrules are collected.
  • JavaScript-family MCP servers are analysed, and SDK forms the analysis cannot follow (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.
  • Audit residuals: the hidden Python MCP syntax-tree cap is the operator limit python-nodes; nested case variants of instruction files are failures; registry and index redirects for inventoried packages (.npmrc including scope, .yarnrc*, bunfig.toml, requirements index options, project pip.conf/pip.ini, uv, Poetry and PDM sources) are recorded with credentials withheld and a low registry_origin finding; the Action's scanners input says naming collectors makes coverage partial.
  • Final review (four parallel adversarial reviews, two skeptics per finding, then an independent check of every fix): every default-ignorable code point is neutralized in output; the unkeyed settings digest binds every host and command and fails on ambiguous URL authorities; symlinked and case-variant chat templates are read or fail; Python launchers and server classes from unrecognized modules and any literal MCP module argument are gaps; trace-diff never matches a call to a tool served at another address and fails on attribute lists over its bound.

Adds evidence features (all offline):

  • digest and SOURCE_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 high runtime_undeclared finding; content attributes are never read; telemetry is recorded as reported, never as verification.
  • Framework crosswalk: every finding category has a SARIF rule; tags add OWASP LLM 2026, MITRE ATLAS 2026.10 and NIST AI RMF; crosswalk reports 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 (profile nexus-symbiote/inventory-content/v1). Without SOURCE_DATE_EPOCH an 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 and twine check) and make 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 pass twine 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 on examples/demo-agent: two scans at different paths are byte-identical and share the content digest; attest/verify-attestation accept 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

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Security fix (see SECURITY.md for reporting guidelines)
  • Refactoring (no functional changes)

Breaking: new coverage gaps and bound settings files can fail a default gate that passed before (see CHANGELOG Changed); a malformed SOURCE_DATE_EPOCH now exits 2; SARIF rules exist for every category, so code scanning shows new rule ids for categories that used the generic rule.

Checklist

make check runs the offline subset of the required CI checks. See CONTRIBUTING.md.

  • Tests cover the new or changed behavior
  • ruff check src tests scripts passes
  • ruff format --check src tests scripts passes
  • mypy src/nexus_symbiote scripts passes
  • Workflow, repository hygiene and lock checks pass (make workflow-check hygiene lock-check)
  • Dependency changes: locks regenerated with make lock, and the "Dependency health" run is green (no dependency changes)
  • Documentation updated (if applicable)
  • CHANGELOG.md updated under Unreleased

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

  • Trust boundary and abuse case stated (each guide's trust model; docs/known-limitations.md and SECURITY.md updated)
  • Regression test fails on the vulnerable behavior (every closed bypass has one, checked against main)
  • Security regression corpus reviewed (scripts/benchmark_security.py; four new hidden-character cases)
  • No new network access, executable discovery, or credential collection without design and tests (every new command is offline and runs under the offline guard in tests/test_modes.py)

Limitations

  • Maintainer decisions left open: settings files bound before this change (Claude Code, Gemini CLI, Windsurf, Cline, .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 exports SOURCE_DATE_EPOCH, so the release evidence now records the commit time.
  • Static analysis of Python and TypeScript MCP sources stays incomplete by nature: computed module names, getattr/globals()/code in strings, and SDK aliases resolved outside the file (Node # imports, tsconfig paths, 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 in docs/known-limitations.md.
  • trace-diff evidence is reported: 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.
  • crosswalk and minimum-elements are 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.
  • Symbolic links that no collector would read stay collection failures: telling a link to a directory from a link to a file would mean examining a path outside the project.
  • Tests that parse Markdown use markdown-it-py, which all three locks carry only through rich; no lock was changed.
  • Only Linux with Python 3.11-3.14 is qualified, as before.

Generated by Claude Code

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
Comment thread src/nexus_symbiote/graph_view.py Fixed
Comment thread src/nexus_symbiote/graph_view.py Fixed
Comment thread src/nexus_symbiote/graph_view.py Fixed
Comment thread src/nexus_symbiote/graph_view.py Fixed
Comment thread src/nexus_symbiote/crosswalk.py Resolved
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
Comment thread src/nexus_symbiote/cli.py Fixed
Comment thread src/nexus_symbiote/core/aibom.py Fixed
Comment thread src/nexus_symbiote/crosswalk.py Resolved
Comment thread src/nexus_symbiote/graph_cli.py Fixed
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
@aisecnomad aisecnomad added documentation Improvements or additions to documentation enhancement New feature or request core Core framework implementation security Security-related changes scanner Scanner module changes standards CycloneDX, SPDX, SARIF, VEX and other standards conformance provenance Build provenance, attestations and evidence binding labels Nov 12, 2026
@aisecnomad aisecnomad added the python Pull requests that update python code label Nov 12, 2026
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 aisecnomad left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 on c5f97be049 (0 commits behind main).
  • 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), and check_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' recomputes content_digest from verified native bytes; --trusted-key enforces st_nlink == 1 and 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.
  • Follow-ups Tracked:
    • Resolving the 2 false-positive crosswalk.py:260 (FRAMEWORK_KEYS) threads (crosswalk_cli.py and test suites import FRAMEWORK_KEYS; __all__ export + trace_evidence.py:_endpoint URL authority guard + macOS APFS case-folding confinement in PR #140 follow immediately).

@aisecnomad
aisecnomad marked this pull request as ready for review November 12, 2026 19:47
@aisecnomad
aisecnomad merged commit b001cb4 into main Nov 12, 2026
19 checks passed
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
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
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Core framework implementation documentation Improvements or additions to documentation enhancement New feature or request provenance Build provenance, attestations and evidence binding python Pull requests that update python code scanner Scanner module changes security Security-related changes standards CycloneDX, SPDX, SARIF, VEX and other standards conformance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants