| 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 |
✅ Fixed — input governance now applied to all message roles; output filtering applied to all response paths including streaming |
| GHSA-v3h4-8458-5ww3 | 6.5 Medium | governance_middleware |
Unauthenticated POST /governance/validate-action endpoint; undermined NIST IA-3/AC-3 control assertions |
✅ 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 |
| 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 |
⚠️ 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.
Alternatively, you may email the maintainers directly. 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.
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.