Skip to content

Software supply-chain hardening

The ecosystem treats workflow code, dependency locks, container inputs, and release credentials as production attack surfaces. The baseline is fail closed: an immutable identity is required wherever external code or an image can run.

Enforced source policy

scripts/check_fleet_supply_chain.py performs a standard-library-only source inspection. It does not import project code, install packages, build binaries, or start services. The gate enforces these contracts:

  • GitHub Actions and reusable workflows use full 40-character commit SHAs.
  • Every workflow declares top-level token permissions; write access is scoped to the job that publishes.
  • write-all, secrets: inherit, pull_request_target, insecure Actions compatibility, and explicitly persisted checkout credentials are rejected.
  • External Docker build stages and COPY --from sources use SHA-256 digests.
  • Compose and stack images use a digest directly or a required environment variable whose prompt requires an image@sha256:<digest> value.
  • Network-to-shell installers, remote ADD, secret-shaped Docker ARG/ENV, privileged containers, host namespace sharing, unconfined profiles, and Docker socket mounts are rejected.
  • Bootstrap scripts may not use eval, Invoke-Expression, or their aliases. Commands are executed as explicit argument arrays so configuration values cannot become shell syntax.
  • Direct VCS dependencies use HTTPS plus a full commit identity. Direct archive dependencies use HTTPS plus a sha256 fragment; branches, tags, and mutable archive URLs are rejected.
  • Python, Node, and Rust dependency manifests have committed resolver locks.

Run the repository gate:

python3 scripts/check_fleet_supply_chain.py .

Run the ecosystem gate from an agent-packages checkout without embedding its machine-specific location in configuration:

python3 "$AGENT_UTILITIES_ROOT/scripts/check_fleet_supply_chain.py" \
  --fleet-root "$AGENT_PACKAGES_ROOT"

Exact source-freeze certification uses the explicit no-Git snapshot mode. The providers root must contain exactly the 71 direct repositories declared by the repository-manager workspace (plus any evidenced local non-provider checkout); the workspace argument is accepted only from its canonical repository-manager/repository_manager/workspace.yml location inside that root:

python3 "$AGENT_UTILITIES_ROOT/scripts/check_fleet_supply_chain.py" \
  --source-snapshot-root "$PROVIDER_SNAPSHOT_ROOT" \
  --snapshot-workspace \
    "$PROVIDER_SNAPSHOT_ROOT/repository-manager/repository_manager/workspace.yml"

Snapshot mode never invokes Git. It rejects symlinks and special files in every declared provider, except for the generated .uv-workspace-siblings/ directory: that directory contains governed local editable-dependency links and is skipped as build plumbing rather than treated as provider source. It retains the normal skipped-directory policy and bounds the provider count, total entries, files, bytes, per-file bytes, and traversal depth. It runs the same workflow, container, installer, dependency-lock, and credential scanners as normal mode. Undeclared or missing direct provider directories fail closed; an unlisted directory is allowed only when its non-symlink .git entry proves it is a local checkout. Provider-root symlinks and symlinks outside the governed workspace-sibling directory fail closed.

Diagnostics contain repository-relative paths only.

Secure bootstrap contract

Review an installer from a release artifact or source checkout before running it. The maintained Bash and PowerShell bootstraps never download and run a second script, never fall back to another package manager, and never evaluate a constructed command string. A separately checked uv executable is required.

Published bootstrap installs use exact agent-utilities and universal-skills versions at the project boundary. Release automation must update those two defaults with each release. A controlled mirror may inject another version only through the documented version variables, which accept exact version syntax and are always passed as one process argument.

An exact top-level package version does not replace a resolver lock or artifact verification. Production and air-gapped releases should install from the certified wheelhouse generated by the release train, check its signed manifest and hashes, and reject packages absent from that manifest.

Agent Utilities uses its in-tree PEP 517 backend for every wheel build. The backend derives a deterministic CycloneDX 1.6 document from the final wheel metadata, embeds it at .dist-info/sboms/package.cyclonedx.json, and rebuilds RECORD exactly. Every Requires-Dist declaration is parsed as PEP 508, and every direct-reference URL is rejected, including file, HTTP, VCS, and SSH transports. Reprocessing an unchanged wheel is byte-for-byte stable.

The publish pipeline proves that stability at the artifact boundary. It builds the wheel twice from the same validated revision into separate output roots with a fixed SOURCE_DATE_EPOCH, applies the release-surface and privacy gates to both candidates, and requires identical filenames and SHA-256 digests. Only the first byte-identical candidate is staged for the Linux and Windows installed-runtime gates; source distributions and unchecked wheel variants never enter the publish artifact.

The backend also opens the packaged, source-controlled dependency license catalog. The catalog uses canonical SPDX expressions for the root project, every emitted direct dependency (including optional test and documentation extras), and declared development groups. Missing, malformed, duplicate, non-canonical, or invalid SPDX entries fail the build; package metadata and the build environment are never consulted for a fallback. Each emitted CycloneDX component carries its reviewed expression, so the synchronized v2 license gate reports neither unknown components nor policy-ambiguous declarations.

LGPL-3.0-only and LGPL-3.0-or-later are admitted only for separately distributed, replaceable optional Python components: PostgreSQL connectivity, the optional Python OWL reasoner, and the optional Telegram adapter. They are dynamically imported rather than copied into Agent Utilities source, and retain their upstream license identity. Permissive MIT-CMU, PSF-2.0, and W3C-20150513 declarations are also explicit. GPL and AGPL identifiers remain terminal denials even inside an OR expression; the prior PDF and transliteration dependencies carrying those licenses were removed rather than excepted.

Create the unified exact input with the packaged assemble-exact-local-release command; temporary or operator-authored assembler scripts are not part of the release path. Derive three component-specific wheelhouses from that validated unified exact closure. The materializer reuses the exact-release specification, evaluates wheel metadata markers and extras, rejects missing or incompatible dependencies, copies only reachable wheels, writes a canonical --require-hashes lock, checks the staged result, and publishes into a new private directory without overwrite. Its JSON result contains only component, count, filename, and content digests—never input or output paths. The output parent must be owner-only on a filesystem that enforces POSIX ownership and mode bits; a mounted filesystem that reports group or world access fails closed.

materialize-component-wheelhouse --component epistemic-graph \
  --release-id "$RELEASE_ID" --spec "$EXACT_RELEASE_SPEC" \
  --source-wheelhouse "$UNIFIED_WHEELHOUSE" \
  --output "$PRIVATE_RELEASE_ROOT/epistemic-graph-wheelhouse"
materialize-component-wheelhouse --component agent-utilities \
  --release-id "$RELEASE_ID" --spec "$EXACT_RELEASE_SPEC" \
  --source-wheelhouse "$UNIFIED_WHEELHOUSE" \
  --output "$PRIVATE_RELEASE_ROOT/agent-utilities-wheelhouse"
materialize-component-wheelhouse --component langfuse-agent \
  --release-id "$RELEASE_ID" --spec "$EXACT_RELEASE_SPEC" \
  --source-wheelhouse "$UNIFIED_WHEELHOUSE" \
  --output "$PRIVATE_RELEASE_ROOT/langfuse-agent-wheelhouse"
Component Root profile Lock filename Exact target
Epistemic Graph epistemic-graph[full] epistemic-graph-requirements.txt release-local
Agent Utilities agent-utilities[serving] release-requirements.txt agent-local
Langfuse Agent langfuse-agent[mcp] langfuse-agent-requirements.txt mcp-local

The Agent Utilities release OCI is built with its agent-local target. Stage only its materialized wheelhouse in release-wheelhouse/. Direct file requirements are forbidden. Pass the lock basename and digest, all three exact root versions, and retain the resulting OCI digest for manifest assembly:

docker build -f docker/Dockerfile --target agent-local \
  --build-arg RELEASE_REQUIREMENTS=release-requirements.txt \
  --build-arg RELEASE_REQUIREMENTS_SHA256="$RELEASE_REQUIREMENTS_SHA256" \
  --build-arg AGENT_UTILITIES_VERSION="$AGENT_UTILITIES_VERSION" \
  --build-arg EPISTEMIC_GRAPH_VERSION="$EPISTEMIC_GRAPH_VERSION" \
  --build-arg LANGFUSE_AGENT_VERSION="$LANGFUSE_AGENT_VERSION" \
  -t agent-utilities:release .

The target checks the requirements file, disables package-index access and uv overrides, requires hashes for the entire closure, and installs roots by package name. It then rejects every PEP 610 direct_url.json, checks all installed root versions, and imports the folded numeric ABI before copying only the installed runtime into the non-root image. No source, credentials, certificates, or machine paths enter the image. The published-wheel agent target remains the normal distribution image; only agent-local may produce the exact OCI subject entered into a release manifest. Materialize that exact local subject through the no-overwrite OCI-layout exporter before component evidence generation.

Build the other two runtime subjects independently from their own minimal closed wheelhouses: epistemic-graph/docker/Dockerfile --target release-local and langfuse-agent/docker/Dockerfile --target mcp-local. All three builds use the same digest-pinned Python and uv bases, but their final root descriptor digests must be pairwise distinct. The compatibility gate rejects a unified image relabeled as multiple components. Exact targets must remain network-independent after their pinned bases are present locally; no mutable OS package index or unpinned package installation may occur in an exact target's ancestry.

Component evidence and assembly

Every release component is materialized with scripts/release/generate_component_evidence.py. The generator opens the actual wheel, catalog, or local OCI-layout archive and binds it to the exact passing source-freeze manifest. An OCI component must supply a non-empty closed wheelhouse. The generator checks every OCI descriptor and blob digest, applies bounded uncompressed or gzip-compressed layers (including whiteouts), and reads the final installed .dist-info/METADATA identities. That installed name/version map must equal the wheelhouse closure; an arbitrary archive, an unreferenced blob, or a missing, extra, or mismatched distribution fails closed. The component artifact identity is the OCI root descriptor digest, while source evidence separately binds the input archive digest.

The generated provenance is an in-toto Statement v1 with the exact SLSA provenance/v1 predicate, one artifact subject, the source-freeze dependency, an exact local build type and an explicit builder id. Source, SBOM, provenance, and signature documents are not merely hashed: the assembly gate enforces their complete semantic schemas and their cross-document component, version, artifact, source, and inventory bindings. Inputs may be local paths, but retained evidence contains only opaque component names and SHA-256 identities. Symlinks, hardlinks, special files, duplicate wheel distributions, ZIP64 wheel metadata, oversized inputs, non-canonical versions, and unbounded adapter output fail closed. Reads bind device, inode, size, modification time, and change time before and after use; writes use same-directory atomic replacement and reject aliases with any input.

Signing and verification are external trust boundaries. The signer command is read from a configured environment variable containing a JSON argument array; no shell is used. It must return the schema-validated, privacy-safe graphos-external-signature/2 bundle, which stores only the signature, the artifact digest, and digests of the canonical composite signing subject, verification material, and signer identity. That subject is the exact canonical JSON declaration containing the component identity, evidence references, capabilities, verifier environment name, and optional entry count. The verifier receives the same subject and bundle bytes that the signer produced, encoded without reinterpretation, and must return both the exact subject digest and artifact digest. Raw certificate identities, endpoints, credentials, and filesystem locations are not retained. Signer and verifier execution has bounded stdout/stderr and a fixed timeout; CLI failures expose only the exception class, never environment-derived error text. Passing --verify-signature invokes the separately configured verifier before accepting the component declaration.

The epistemic-operations-protocol release component (retired, its generated DTOs replaced by the engine's own client) is a frozen declaration in the compatibility matrix: its generator (scripts/check_epistemic_operations_protocol.py) and the catalog it produced (agent_utilities/protocols/epistemic_operations/schemas/v1/catalog.json) were retired once every DTO they generated was replaced by a direct import of the engine's own generated client (epistemic_graph.generated.models). Release tooling pins the component's last-generated version and digest rather than regenerating bytes from a now-deleted catalog; retiring the matrix declaration itself is release governance, tracked separately from this deletion.

After all eight declarations exist, scripts/release/generate_release_assembly.py requires the exact component set from the compatibility matrix, the canonical source-freeze evidence reference, and all six current certification references: the connector ledger, pre-bundled skill matrix, exact skill-validation deployment document, signed skill-validation lifecycle evidence, exact-artifact closure, and signed OCI vulnerability-scan evidence. It opens every evidence reference and emits the deterministic ReleaseAssembly; no certification input is optional or accepted through a legacy field. The existing assemble_manifest.py independently schema-validates the freeze, reconstructs its snapshot and evidence digests, requires ordered passing G-01 through G-39 records, and matches those digests to every component before producing the unsigned local binder. A signed release still requires the authorized external signer, live connector ledger, and production certification evidence; local tooling never fabricates those gates.

The generator accepts the two skill lifecycle authorities only through --skill-validation-deployment and --skill-validation-lifecycle-evidence. Both are release-relative references to new, unaliased evidence files; the manifest retains their independent SHA-256 digests and uses only the lifecycle digest as an exact-gate authority.

The source freeze is accepted only through --source-freeze-evidence. It is a release-relative reference retained exactly once in the manifest; repeated digest claims in component source documents are not an independent source authority.

For a host-local hot swap, use the current-only exact local GraphOS release promoter. It applies the same package-name-only and hash-locked closure policy, also checks installed RECORD ownership and native artifact digests, and atomically rolls current back when its bounded canary or doctor proof fails.

Dependency lock inventory

scripts/audit_fleet_dependencies.py deduplicates exact coordinates across all committed uv.lock, Cargo.lock, and pnpm-lock.yaml files and checks artifact identity. It never invokes a resolver or package manager and makes no network requests: every Python registry artifact must use HTTPS with a SHA-256 digest, every Cargo registry package must use HTTPS with a 64-character checksum, and every pnpm registry package must carry a valid SHA-512 integrity value. A version-only lock entry is not considered release evidence.

python3 "$AGENT_UTILITIES_ROOT/scripts/audit_fleet_dependencies.py" "$AGENT_PACKAGES_ROOT"

Vulnerability lookups against external advisory services are not part of any blocking gate: gates must run identically offline, locally, and in CI. Review upstream advisories when refreshing a lock, and raise the affected floor in pyproject.toml with a comment naming the advisory.

Updating immutable references

An immutable pin still requires review. For each update:

  1. Check the upstream repository and release provenance.
  2. Resolve the release tag to its commit or the image manifest to its digest.
  3. Review the diff between the old and new identities.
  4. Update the adjacent human-readable version comment without replacing the immutable identity.
  5. Run the repository and fleet source gates, then the vulnerability audit.

Reusable workflows are transitive code. Pinning the caller prevents silent replacement, but the reusable workflow repository must independently apply the same action-pinning, permission, lock, and release controls.

Lock remediation order

Never edit a generated lock by hand. When a source constraint changes, refresh only that repository's lock, review the manifest and lock together, rerun the exact-coordinate lock inventory, and complete its tests before moving to the next repository. Keep resolver and native-build concurrency at one on constrained workstations. Missing locks are release blockers, not warnings.

If an advisory has no fixed upstream release, do not invent a version floor. Disable or isolate the affected capability until a patched artifact is available.

Secrets and release permissions

Secrets are injected at runtime through the deployment secret provider. They are not Docker build arguments, image environment layers, Compose defaults, logs, or repository files. Reusable workflows receive named secrets only. Publishing jobs get the narrow token scope they need; all other jobs retain contents: read or an empty permission set.