| Version | Supported |
|---|---|
| 3.1.x | ✅ Yes |
| 3.0.x | |
| 2.1.x | |
| < 2.1.0 | ❌ No |
The following advisories were identified and remediated prior to their respective releases. Documented here for transparency.
| Advisory | CVSS | Component | Description | Status |
|---|---|---|---|---|
| GHSA-hfqj-24cj-693g | 9.4 Critical | inference_proxy |
Governance bypass: crafted requests with no role: "user" message, or stream: true responses, could reach the LLM backend without passing input/output governance tiers (reported by @uwezkhan) |
✅ Fixed — input governance now applied to all message roles; output filtering applied to all response paths including streaming |
| GHSA-529c-23c8-3m42 / GHSA-qrg7-27x4-mq39 | 8.6 High | compliance_bridge / defer_queue |
Dual-control quorum bypass via unverified x-cage-source-principal header in require_operator_identity, CAGE_ENV="development" default in require_internal_token / K8s manifest, and uninvoked verify_webauthn_challenge() in DeferQueue.approve() (independently reported by @moiznationzz and @Zorta-Zenith) |
✅ Fixed — x-cage-source-principal removed; operator SVID bound strictly to Linkerd proxy-verified l5d-client-id (or transport request.attributes.source.principal) with optional CAGE_TRUSTED_OPERATOR_IDENTITIES allowlist; require_internal_token defaults to fail-closed "production" and requires dual-condition opt-in (CAGE_ALLOW_DEV_UNAUTHENTICATED=true) in dev; verify_webauthn_challenge() enforced in DeferQueue.approve() |
| GHSA-4x3x-w2m3-8mfh | 7.5 High | mcp_tool_server / docker-compose.yml |
Unauthenticated trigger_safety_intervention execution via POST /tools/execute when docker-compose.yml overrode the gateway entrypoint to mcp_tool_server.py (bypassing hybrid_server.py's WorkloadIdentityMiddleware), setting a permanent Redis safety_violation lock (reported by @m0ssser) |
✅ Fixed — docker-compose.yml and docker-compose.dev.yml invoke hybrid_server.py with 127.0.0.1 host port binding; mcp_tool_server.py enforces Linkerd workload identity on /tools/execute and standalone __main__; trigger_safety_intervention sets bounded TTL (CAGE_SAFETY_INTERVENTION_TTL_SECONDS) |
| GHSA-cmwm-295c-gjf6 / GHSA-w7g7-wjj9-6j97 | 7.5 High | cbf |
Reconciled/KMS-signed balance (verified_balance_payload) disconnected from atomic_verify_and_commit(), allowing Lua commit against raw balance key (reported by @ArksherX) |
✅ Fixed (ADR-010) — _resolve_ground_truth_balance() verifies KMS signature and syncs verified balance before Lua commit |
| GHSA-w7f4-8grh-2xv8 | 6.5 Medium | governor / cbf |
Auto-approved trade path called stateless cbf.evaluate() without atomic Redis deduction (atomic_verify_and_commit()), enabling concurrent TOCTOU balance bypass (reported by @ArksherX) |
✅ Fixed — Phase-2 commit_barrier executes atomic_verify_and_commit() on the single committing run inside SymbolicGovernor.govern() |
| GHSA-v3h4-8458-5ww3 | 6.5 Medium | governance_middleware |
Unauthenticated POST /governance/validate-action endpoint; undermined NIST IA-3/AC-3 control assertions (reported by @uwezkhan) |
✅ Fixed — callers must present a trusted Linkerd mTLS workload identity before any processing (WorkloadIdentityMiddleware; POAM-2026-080 replaced the original HMAC routing-seal check); rate limiting added |
| GHSA-w39v-5v7h-fxg9 | 5.3 Medium | execute_trade_action / governed trader |
Model-controlled trader_role raised OPA fiscal limits on the committing trade path: the role and trader_id were agent-facing tool arguments that reached trade_governance.rego unchanged, so a prompt-injected senior lifted the unreviewed limit from $5,000 to $500,000 (found during maintainer triage) |
✅ Fixed — role and trader bound server-side from the verified workload identity; removed from the tool schema; model-supplied deferred_id stripped |
| GHSA-f6xq-23f7-9grm | 5.3 Medium | governor (POST_HITL) / fria tier / trade_governance.rego |
A normative-provider determination revoked while a human reviewed a held request was never re-checked after approval, so the approved request was sealed; execute_trade_bounded bypassed the trade policy's RBAC ladder, and case or whitespace variants (btc, BTC ) bypassed its BTC restriction; the FRIA payload named no caller, so provider_01 could not mint a ConsequenceToken (reported by Dominik Blain, @dom-omg, Ironproof) |
✅ Fixed — jurisdiction tiers re-run after approval and a hold is answered only by an approval given against it (OPA's manual review included); every trade action is held to the RBAC ladder, currencies are an allowlist and assets are compared normalised; the payload carries the verified workload identity and trace id. Provider-side request facts tracked as POAM-2026-113 |
| CAGE-AUDIT-B3 | 7.5 High | attestation_provider |
Attestation Failure Attributability: Attestations lacked structural attribution, allowing misbehaving external providers to crash the aggregator silently and fail open or obscure the source. | ✅ Fixed (POAM-2026-072) — provider_name and fetch_error attribution added. Ed25519 CER signature verification added for fail-closed security enforcement. |
| CAGE-AUDIT-B2 | 7.5 High | routing_seal |
Evidence sufficiency gap: un-bound seals could authorize execution without verifiable cryptographic link to durable audit record | ✅ Fixed — HMAC Routing Seal v2 embeds SHA-256 record_hash in 4-tuple token; binding is required by default in every posture (CAGE_REQUIRE_EVIDENCE_BINDING=false is honoured only outside production), and consumption checks the seal's record_hash against an issuance-time evidence index |
| CAGE-AUDIT-P0 | 7.8 High | cbf |
Replication split-brain double-spend: async Redis failover could expose stale balance | ✅ Fixed — _sync_to_replicas() via WAIT with automatic fail-closed rollback (rollback_state()) on replica timeout in production |
| #511 | — | evidence stream |
Duplicate evidence records after a lost Redis reply (the re-sent compare-and-append re-sealed its own batch at the next sequence), and silent re-genesis of the chain after the stream was truncated or emptied (reported by @danofairanks) |
✅ Fixed — a re-sent batch answers ALREADY, a CONFLICT checks whether the batch already landed, and an unanswered append is re-sent verbatim, never re-sealed; restore verifies the head hash and raises EvidenceChainCorruptError instead of starting over under a live chain; the custodian records a chain rotation as a CHAIN_ROTATION gap |
| #514 | — | mcp_client / execute_trade_action |
MCP tool calls were blindly retried after an unknown outcome, and a retried trade was a second, fully governed execution (reported by @danofairanks) |
✅ Fixed — GatewayMCPClient.call_tool() resends a call only when nothing was sent or the gateway annotates the tool read-only or idempotent, and otherwise raises MCPToolOutcomeUnknownError; execute_trade_action requires a UUIDv4 or UUIDv5 transaction_id and claims it in the kernel ExecutionLedger before governance, so a resubmission gets the first outcome back instead of executing again |
⚠️ Reference architecture notice: CAGE is a reference architecture and is not deployed to production. These advisories are tracked for completeness and to ensure the codebase accurately represents the security posture claimed in associated research publications.
Please do not report security vulnerabilities through public GitHub issues.
To report a security vulnerability, please use the GitHub Security Advisory "Report a Vulnerability" feature. Please include:
- A description of the vulnerability and its potential impact
- Steps to reproduce the issue
- Any proof-of-concept code (if applicable)
- Your suggested fix (if you have one)
You should receive a response within 5 business days. If you do not receive a response, please follow up to ensure your report was received.
We follow a coordinated disclosure model:
- You report the vulnerability privately.
- We confirm receipt and begin investigation within 5 business days.
- We develop and test a fix.
- We release the fix and publish a security advisory.
- You may publicly disclose the vulnerability after the fix is released, or after 90 days from the initial report — whichever comes first.
An advisory's fix lands from GitHub's temporary private fork, where GitHub
Actions does not run. Ruleset 25966171 requires eight status checks on main
and has no bypass actor, so it blocks that merge. Do not add a bypass actor.
Ruleset 26377312, which blocks force pushes to main and its deletion, stays
enabled throughout: the merge is a fast-forward. For that one merge:
- Run the test suite on the frozen fix commit and record the result in the advisory's release record, since the private fork gets no CI.
- Disable the checks ruleset:
gh api -X PUT repos/google/cybernetic-agent-governance-engine/rulesets/25966171 -f enforcement=disabled - Merge the advisory's pull request, then re-enable the ruleset at once:
gh api -X PUT repos/google/cybernetic-agent-governance-engine/rulesets/25966171 -f enforcement=active - Record both ruleset-history entries
(
gh api repos/google/cybernetic-agent-governance-engine/rulesets/25966171/history) and the merge commit in the advisory's release record. - Watch the first CI run on
mainfor the landed commit, and fix forward if it fails.
The following are in scope for security reports:
- Remote code execution in the governance gateway or compliance bridge
- Authentication/authorisation bypass in the governance pipeline
- Governance tier bypass (violations of the NoDirectBind invariant)
- Injection vulnerabilities (prompt injection, SQL injection, etc.)
- Cryptographic weaknesses in the Cloud KMS signing, kid-resolved trust anchors, development-only software signers (Ed25519 / HMAC-SHA256), routing seals, or the SHA-256 hash-chain implementation
- Control Barrier Function (CBF) race conditions or invariant violations
- Secrets or credentials exposed in the repository
The following are out of scope:
- Vulnerabilities in third-party dependencies (report these upstream)
- Denial-of-service attacks requiring physical access
- Social engineering attacks
- Issues in documentation only
CAGE is a domain-agnostic governance substrate designed for regulated
environments generally; the shipped example domains (finance and healthcare)
and the selectable jurisdictional postures (US_FED, EU_ECB, APAC_MAS,
LOCAL) determine which regulatory controls are in scope for a given
deployment. Key security controls are documented in:
docs/security/SECURITY_STATUS.md— full security posture, NIST RMF status, and all open POA&M itemsdocs/operations/KEY_ROTATION.md— cryptographic key lifecycle and rotation runbooks (SC-12 / IA-5)docs/architecture/GATEWAY_ARCHITECTURE.mddeployment/k8s/K8S_SECURITY_HARDENING.mdCOMPLIANCE.md
| Control | Implementation |
|---|---|
| Governance signing | Cloud KMS asymmetric signing (cloud providers in src/integrations/{gcp,aws,azure}/kms_provider.py, loaded via signer_factory.py); separate keys per role (gateway seal, reconciler snapshot, compliance-bridge evidence) with kid-resolved verification. Software Ed25519 / HMAC signers are refused under an enforcing posture and allowed only in dev/test/CI. 90-day rotation cadence per KEY_ROTATION.md |
| Routing seal v2 | 4-tuple token <expire_hex>.<action_slug>.<record_hash_hex>.<hmac_hex> binding SHA-256 evidence record hash (KMS-signed JWT seals when a KMS signer is active); internal to /tools/execute and ConsequenceGateway — not used for caller authentication |
| TLS & Transport Security | NIST SP 800-52 Rev. 2 minimum TLS 1.2+ validation, OIDC JWKS verify=True enforcement, and Linkerd mTLS manifest policies (tests/test_tls_enforcement.py) |
| Base Image Hardening | Advisor, gateway, compliance-bridge and NeMo Guardrails images use two-stage builds on a digest-pinned Wolfi base (cgr.dev/chainguard/wolfi-base) with no compiler, util-linux or package-manager cache in the runtime stage and a build-time apk upgrade; images are tagged by git SHA and their digests signed for Binary Authorization at build time (scripts/build_images.sh) |
| Prompt injection detection | Aho-Corasick O(n) scan; 14+ patterns |
| PII protection | Presidio; 15 entity types; input + output |
| Human-in-the-loop | Redis-persisted checkpoint; TOCTOU remediation via post_hitl_rehydrate + post_hitl_revalidate (advisor calls the gateway's POST /governance/revalidate-post-hitl) |
| Control Barrier Function | Atomic Redis Lua (atomic_verify_and_commit()) with synchronous replica WAIT barrier, monotonic safety:fence_epoch with a shared high-water mark (safety:fence_epoch_hwm), and fail-closed state rollback |
| Evidence chain integrity | SHA-256 hash-chained NDJSON & Redis Streams db=1 (noeviction, Lua compare-and-append); enforced blocking durability in production (validate_evidence_stream_preconditions()); compliance-bridge EvidenceCustodian KMS batch attestation (EVIDENCE_KMS_KEY) to WORM storage and CustodyVerifier read-back verification (kid-resolved trust anchors, assert_citable() OSCAL citation gate) |
| mTLS | Linkerd mTLS with a Google CAS trust anchor (infra/modules/service_mesh); gateway ingress restricted by Linkerd Server / HTTPRoute / AuthorizationPolicy; one KSA/GSA per workload |
| Caller identity | Gateway ingress requires a Linkerd-verified l5d-client-id that matches CAGE_TRUSTED_CLIENT_IDENTITIES (WorkloadIdentityMiddleware); enforced in every environment, no header/body identity fallback, fail-closed 403. An RFC 9449 DPoPValidator (src/gateway/server/dpop_validator.py) ships and is unit-tested but is not yet wired into any ingress path. See docs/architecture/AGENT_IDENTITY_BINDING_SPEC.md |
| Egress credentials (v3.1.0) | CredentialBrokerAdapter protocol in Layer 1; the Layer 3 reference actuator fetches per dispatch, keyed on agent identity and tool name, and attaches the result as request headers. Values are masked in logs and absent from the audit record; fails closed on CredentialNotFound / CredentialAccessDenied and records ACTUATION_REFUSAL_RECEIPT via ingest_actuation_receipt() |
| Egress lockdown | GKE Dataplane V2 NetworkPolicy + FQDNNetworkPolicy allowlists; DNS egress restricted to kube-dns and Cloud DNS (deployment/k8s/cilium/egress-lockdown.yaml) |
| Token quota enforcement | Per-session step-count (≤12) and token (≤100k) via Redis atomic Lua counters; fail-closed |
Note: CAGE v3.x is a reference architecture. Regulated-environment deployers must conduct their own risk assessment before production use. See
docs/security/SECURITY_STATUS.mdfor the complete posture breakdown and pre-deployment checklist.
Replay-attack closure: KMS-signed reconciliation payloads embed a
signed_atUnix timestamp. The verifier (KMSGovernanceSigner.verify()inkms_signer.py) rejects any payload wherenow - signed_at > 300 s(MAX_KMS_PAYLOAD_AGE_SECONDS).
Strict Replication & Fence Epoch Hardening: In production (
CAGE_ENV=prod),ControlBarrierFunctionasserts synchronous replica replication (CAGE_STRICT_REPLICATION=true). If replica synchronization fails duringWAIT, the local balance debit is automatically rolled back (rollback_state(cost)) and fails closed. Monotonicsafety:fence_epochcounters prevent stale-replica balance replays.
Audit Durability & Citation Guarantee:
validate_evidence_stream_preconditions()is posture-based: under an enforcing posture (anything but dev/test/ci; an unsetCAGE_ENVis production) it halts startup ifEVIDENCE_STREAM_ENABLED=false, or ifEVIDENCE_CHAIN_BLOCKING=falsewithout an explicitCAGE_ALLOW_NONBLOCKING_PROD=true. The gateway lifespan also starts the sink viastart_evidence_sink()and fails closed, so no routing seal orConsequenceGatewayEXECUTEverdict is issued without durable evidence commitment to the tamper-evident log. In the compliance bridge,EvidenceCustodiansigns batches withEVIDENCE_KMS_KEYand writes them viaput_if_absent()to WORM cold storage, whileCustodyVerifier(evidence_verifier.py) re-verifies the archive onEVIDENCE_VERIFY_INTERVAL_Sagainstkid-resolved trust anchors and gates OSCAL assessment citations (OSCAL_REQUIRE_VERIFIED_CUSTODY=true).
Header-spoofing closure: Caller identity is never read from application-layer data.
X-Agent-IDheaders, body-suppliedagent_idfields, and theX-CAGE-Routing-Sealheader are not accepted as authentication. The sole source of truth is thel5d-client-idheader, which the Linkerd inbound proxy sets from the verified mTLS peer certificate (or strips when there is none). The gateway accepts exactly one such header matchingCAGE_TRUSTED_CLIENT_IDENTITIES, in every environment; anything else fails closed with 403 rather than falling back to an anonymous principal. The meshAuthorizationPolicyindependently admits only the advisor's service account (POAM-2026-080).
Ambient-credential closure: Outbound API credentials are no longer held by adapters. They are requested per dispatch from a
CredentialBrokerAdapter, keyed on the calling agent's SVID and the tool name, injected as request headers at dispatch time, and held only in local scope. Credential values never appear inActuationReceipt, findings, or HTTP response bodies — asserted bytest_credential_headers_not_in_audit_record.Masking is prefix-preserving, not total. Log masking applies
value[:8] + "****". For anAuthorization: Bearer <token>header only the scheme survives, but a broker that returns a bare-token header will leak the first 8 characters of the secret into logs. Brokers should return scheme-prefixed header values.
The following patterns are strictly forbidden in production code and will be rejected in review:
- PROHIBITED:
secret = os.environ.get("API_KEY", "default-secret") - REQUIRED: Fail-closed validation asserting
os.environ["API_KEY"]exists and meets minimum entropy length (>= 32 chars).
- PROHIBITED:
subprocess.run(command, shell=True) - REQUIRED:
subprocess.run(shlex.split(command))with direct argument lists.
- PROHIBITED:
requests.get(url, verify=False) - REQUIRED: Explicit TLS certificate verification (
verify=True).
- PROHIBITED:
pickle.loads(data)or unconstrainedyaml.load(data) - REQUIRED:
json.loads(data)oryaml.safe_load(data).
All prohibited patterns are enforced via static grep checks, pre-commit hooks, CI security jobs, and targeted unit regression suites in tests/test_*_security.py.
The CAGE maintainers thank the following researchers for reporting security and governance boundary observations with reproducible test cases:
- Daniel Fairbanks (
@danofairanks) — Boundary analyses and hardening reports across CAGE's runtime governance, provenance, and offline evaluation planes (#379,#405,#406,#463,#464,#465,#472,#473,#511,#512,#513,#514,#516), including trajectoryNARROWvs.ALLOWmetric separation, multi-turn shadow-state tier parity,RefusalReceiptproof_hashverification and DPOchosen-argument sanitization, offline drawback-detector,SKILL.mdstatic-linter, rollout-runner symbol/HTTP bounds, fail-closed Stage 2 certification gating (--stage0-only), positive-lift and drawback enforcement inrun_certification_gate(),STPAValidatoraction normalization / approval-token validation / core-rule preservation,actuator_02signed-receipt enforcement, FTRANaN-confidence guards, FTRA registryexpires_atenforcement, retry-safe evidence stream appends and chain-truncation detection, stage- and evidence-bound.cerskill-certification receipts (cage.skill.cer.v2), full-order routing-verdict reuse and multi-step trade plan previews, MCP send-phase retry boundaries andExecutionLedgertrade idempotency, and explicit end-user principal assertion boundaries. @moiznationzz— Reported the compliance-bridge dual-control quorum bypass via unverifiedx-cage-source-principalheaders,CAGE_ENVdefault authentication bypass indeployment/k8s/compliance-bridge.yaml, and uninvokedverify_webauthn_challenge()inDeferQueue.approve()(GHSA-529c-23c8-3m42).@Zorta-Zenith— Independently reported thex-cage-source-principalmulti-party quorum forgery and OIDC fallback gap insrc/compliance_bridge/auth.py(GHSA-qrg7-27x4-mq39).@m0ssser— Reported thedocker-compose.ymlentrypoint override exposingmcp_tool_server.py'sPOST /tools/execute(trigger_safety_intervention) withoutWorkloadIdentityMiddleware(GHSA-4x3x-w2m3-8mfh).@ArksherX— Reported Control Barrier Function (CBF) ground-truth balance verification and atomic reservation boundary findings (GHSA-cmwm-295c-gjf6,GHSA-w7g7-wjj9-6j97,GHSA-w7f4-8grh-2xv8).@uwezkhan— Reported the inference proxy multi-role / streaming governance bypass (GHSA-hfqj-24cj-693g) and/governance/validate-actioningress authentication boundary (GHSA-v3h4-8458-5ww3).- Dominik Blain (
@dom-omg), Ironproof — Reported, with step-by-step reproductions, that a normative-provider determination revoked during human review was never re-checked after approval (the EU_ECBfriatier did not run under POST_HITL), thatexecute_trade_boundedand case or whitespace variants ofBTCbypassedtrade_governance.rego, and that the FRIA payload named no caller and the provider_01 request relied on self-asserted mandate facts (GHSA-f6xq-23f7-9grm, POAM-2026-113). Thank you for a careful, clearly reasoned report.