Security
Threat model
The trust boundaries, the adversaries we model, what each control does and does not stop, and the residual risk we accept.
Last reviewed 31 August 2026
Formal Threat Model — CAIN Trust Fabric / CAIN Studio / MCPGate
Version 1.0 · 2026-08-16 · Classification: shareable under NDA
This model was produced by reading the running code and probing the live system, not by populating
a template. Every control marked *implemented* names the file or endpoint that implements it and,
where one exists, the test that proves it. Every gap is stated as a gap.
Standing assumption for the whole document: the platform is a single-node deployment on a
shared VM. That is a material fact for availability and blast-radius analysis and is not hidden
below.
1. System decomposition and trust boundaries
┌──────────────────────────────────────────────────── TB-1 · public internet ──┐
│ Agent runtime (customer-controlled, UNTRUSTED) │
│ LangChain / LangGraph / CrewAI / OpenAI / custom loop │
└───────────────────────────┬──────────────────────────────────────────────────┘
│ HTTPS + X-API-Key, or MCP (streamable HTTP)
┌───────────────────────────▼───────────────────── TB-2 · enforcement boundary ┐
│ k3s / Traefik ingress → platform-gateway (4 uvicorn workers) │
│ entitlement · rate limit · containment · OPA · Fabric decision · metering │
│ /internal/* namespace block │
└───────────────────────────┬──────────────────────────────────────────────────┘
│ cain-net Docker bridge (TRUSTED-ISH, see R-7)
┌───────────────────────────▼───────────────────── TB-3 · service mesh ────────┐
│ 99 product services · OPA · adversarial-fuzzer · actionproof · agent-id │
│ billing-service (money) · cainaccounts (humans) · blizzard-governance │
└───────────────────────────┬──────────────────────────────────────────────────┘
│
┌───────────────────────────▼───────────────────── TB-4 · state ───────────────┐
│ gateway.db (decisions, principals, containment) · billing db (money) │
│ Docker volumes · Stripe (external, PCI-delegated) │
└──────────────────────────────────────────────────────────────────────────────┘
Trusted: the gateway process, the cain-net bridge, the state volumes, Stripe.
Untrusted: every agent runtime, every payload, every header the caller controls (including
X-CAIN-Chain-Id and X-CAIN-Chain-Depth), every upstream tool *response*.
Semi-trusted: the 99 product services — they are ours, but several assume /internal/* is
unreachable from outside, which is why the gateway blocks that namespace centrally rather than
trusting each service to defend itself.
2. STRIDE
| # | Threat | Category | Surface | Status | Control / evidence |
| S-1 | Forged tenant identity via a guessed/stolen API key | Spoofing | TB-2 | Mitigated | Live entitlement check per request, fail-closed; keys stored only as SHA-256 fingerprints in the Fabric registry (fabric_control_plane.py::_fingerprint) |
| S-2 | Agent self-asserting a privileged principal | Spoofing | TB-2 | Mitigated | Principals are materialised from billing facts, never from caller input; agent sub-keys cannot register or revoke principals (test_agent_sub_keys_cannot_mint_or_revoke_principals) |
| S-3 | Forged X-CAIN-Chain-Id / depth | Spoofing | TB-2 | Partially mitigated, documented | Depth is *cooperative*; chain ids are sanitised and length-bounded; the tenant velocity breaker is unforgeable because the tenant comes from billing (test_forged_chain_ids_are_sanitised_not_trusted) |
| S-4 | Signature forgery on a delegation token | Spoofing | TB-3 | Mitigated | Ed25519 via cryptography; verification returns 503 rather than a meaningless true if the library is absent (test_real_ed25519_signature_verification) |
| T-1 | Tampering with a decision record after the fact | Tampering | TB-4 | NOT mitigated — stated | The decision store is durable, not signed or tamper-evident. Cryptographic trails are separate products (QuorumSeal, blizzard-governance signed audit) |
| T-2 | SQL injection via chain id / principal id | Tampering | TB-4 | Mitigated | Parameterised statements throughout; chain ids character-restricted (test_forged_chain_ids_are_sanitised_not_trusted passes '; DROP TABLE ...) |
| T-3 | Poisoned MCP tool definition changing under the caller | Tampering | TB-1/TB-3 | Mitigated (self-hosted) | DriftGuard pin/verify/diff; MCP Security Scanner. Hosted gap: no equivalent for model/tool pinning — Phase 4 in SUPERLAYER_GAP_ANALYSIS.md |
| T-4 | Malicious tool *output* steering the next agent step | Tampering | TB-1 | Partially mitigated | Injection screen runs on request payloads; talos-shield can screen text; there is no mandatory screening of upstream responses on the hosted path |
| R-1 | Tenant denies having authorised an action | Repudiation | TB-4 | Mitigated for decisions | Every gated call writes a decision record with per-stage verdicts, retrievable by id, explainable via /fabric/decisions/{id}/explain with a content-addressed policy version |
| R-2 | Operator changes policy silently, then denies it | Repudiation | TB-3 | Mitigated | Policy version is sha256 over the rules OPA actually serves; a silent change changes the id (test_policy_version_changes_when_the_rules_change) |
| I-1 | Cross-tenant data disclosure | Info disclosure | TB-2/TB-4 | Mitigated | Every Fabric/AgentOps/policy query filters on the tenant resolved from the caller's own key; another tenant's id reads as 404, not 403 (test_principals_are_tenant_scoped, test_summary_is_tenant_scoped, test_dryrun_is_tenant_scoped) |
| I-2 | Secrets in logs or URLs | Info disclosure | TB-2 | Mitigated | Keys travel as headers; the AgentOps page asserts no key in any URL (test_page_renders_and_never_puts_the_key_in_a_url); raw keys never stored |
| I-3 | /internal/* service namespaces reachable through the proxy | Info disclosure / EoP | TB-2 | Mitigated | Whole namespace blocked centrally at the gateway before auth is even considered |
| I-4 | PII leaving via an agent payload | Info disclosure | TB-1 | Partially mitigated | pii-redactor, LeakGuard (self-hosted) exist as services but are not mandatory in the call path |
| D-1 | Runaway agent exhausting capacity/cost | DoS | TB-2 | Mitigated | Chain depth, per-chain call budget, tenant velocity breaker; proven live to terminate an unbounded chain in 24 calls |
| D-2 | Single-node failure taking the platform down | DoS | TB-2/TB-3 | NOT mitigated — stated | One VM, no multi-node redundancy. See RELIABILITY_SRE.md |
| D-3 | SQLite write contention under load | DoS | TB-4 | Partially mitigated | WAL, per-process connections, amortised pruning; measured ceiling ~83 rps at concurrency 24 |
| E-1 | Agent sub-key escalating to parent privileges | EoP | TB-2 | Mitigated | Agent keys cannot mint principals, revoke principals, or mint delegation tokens; tier and budget inherited, never elevated |
| E-2 | Budget bypass by parallelism | EoP (economic) | TB-2/TB-4 | Mitigated (was exploitable) | Was: 64/64 concurrent calls granted against a 10-call budget (540% over). Now atomic reservation; live proof 4 granted of 40 |
| E-3 | Confused deputy — gateway using its own authority for a caller | EoP | TB-2/TB-3 | Mitigated | ActionProof is called with the *caller's* key, not a gateway credential; the gateway holds no shared secret for it |
3. LINDDUN (privacy)
| # | Threat | Status | Notes |
| L-1 | Linkability of calls to a person | Partial | Decisions link to a principal and tenant. Human principals exist only when a tenant registers them; the platform does not derive natural-person identity on its own |
| I-5 | Identifiability from stored payloads | Mitigated | Decision records store service, path, verdicts — not request bodies |
| N-1 | Non-repudiation used against a data subject | Accepted | Evidence exists by design; retention is bounded (default 90 days) and configurable |
| D-4 | Detectability of whether a principal exists | Mitigated | Cross-tenant lookups return 404, not 403 — existence is not disclosed |
| D-5 | Disclosure of information | See I-1…I-4 | |
| U-1 | Unawareness — data subjects unaware of processing | Partial | /privacy states processing; content-provenance issues EU AI Act Art. 50 disclosure manifests. No per-data-subject notice mechanism |
| N-2 | Non-compliance with stated policy | Mitigated | Retention is enforced in code (DECISION_RETENTION_DAYS), not only documented |
Pseudonymisation and rehydration (named in the brief): the platform does not implement a
pseudonymisation/rehydration pipeline. pii-redactor performs one-way redaction with no
rehydration key. This is a gap, not a control, and any claim to the contrary would be false.
4. MITRE ATT&CK mapping
Enterprise techniques relevant to an agent-execution boundary:
| Technique | ID | Relevance | Control |
| Valid Accounts | T1078 | Stolen API key | Live entitlement check; per-agent revocation; key rotation (/subscribe/rotate-key) |
| Exploit Public-Facing Application | T1190 | The gateway is the public surface | Single entry point; /internal/* block; security headers; CSP |
| Command and Scripting Interpreter | T1059 | Agent-driven code execution | Hosted: no general code execution primitive is exposed. Self-hosted: ToolWarden sandbox |
| Server Software Component | T1505 | Malicious MCP tool | DriftGuard pinning, MCP Security Scanner (self-hosted) |
| Unsecured Credentials | T1552 | Keys in logs/URLs | Fingerprint-only storage; header-only transport |
| Automated Collection | T1119 | Agent mass-reading tenant data | Tenant scoping; velocity breaker |
| Resource Hijacking | T1496 | Runaway/abusive compute or spend | Containment + atomic spend caps |
| Data from Information Repositories | T1213 | Cross-tenant read | Tenant-scoped queries throughout |
| Impair Defenses | T1562 | Disabling the gate | Enforcement flags are deployment config, not caller-controllable; /fabric/status reports live mode |
Not applicable / not claimed: persistence, lateral movement and exfiltration techniques
targeting the host OS are outside what this platform controls. There is no EDR, no host IDS, and
none is claimed.
5. Attack trees — catastrophic scenarios
AT-1: Unauthorised real-world action executed with evidence showing it was authorised
GOAL: cause an unauthorised action AND make the record say it was fine
├── 1. Bypass the boundary entirely
│ ├── 1.1 Call the upstream service directly ......... blocked: services expose no host ports;
│ │ gateway is the only published entry
│ ├── 1.2 Reach /internal/* through the proxy ........ blocked centrally at the gateway
│ └── 1.3 Compromise the cain-net bridge ............. REQUIRES host compromise (R-7, residual)
├── 2. Pass the boundary with forged authority
│ ├── 2.1 Forge a tenant id ......................... blocked: tenant derives from billing, not input
│ ├── 2.2 Steal an API key .......................... POSSIBLE (T1078) → mitigations: rotation,
│ │ per-agent budgets, revocation, evidence
│ └── 2.3 Escalate from an agent sub-key ............ blocked: agent keys cannot mint/revoke
├── 3. Make the decision say yes
│ ├── 3.1 Exploit fail-open on a dependency ......... blocked: unavailable ≠ allow; entitlement
│ │ fails closed; containment fails closed
│ ├── 3.2 Exploit "no profile" as permission ........ NOT a denial by design, and enforcement is
│ │ opt-in — this is the honest weak point:
│ │ a tenant with no ActionProof profile gets
│ │ no plan-level protection
│ └── 3.3 Race the budget check .................... FIXED (E-2); was exploitable
└── 4. Corrupt the record afterwards
├── 4.1 Edit the decision store ................... POSSIBLE with host/db access (T-1) —
│ store is not tamper-evident
└── 4.2 Change policy and deny it ................. blocked: content-addressed policy version
Residual for AT-1: a stolen root API key plus a tenant that has authored no ActionProof profile
and left enforcement in shadow mode. Every layer then records faithfully and blocks nothing. The
compensating controls are per-agent budgets, revocation, and the fact that the evidence trail makes
the action attributable after the fact.
AT-2: Financial exhaustion of a customer
GOAL: run up unbounded cost on a victim tenant ├── 1. Runaway loop .................................. contained (depth/chain/velocity) ├── 2. Parallel budget bypass ........................ FIXED (atomic reservation) ├── 3. Chain-id rotation ............................. falls through to tenant velocity breaker ├── 4. Many agent keys under one tenant .............. PARTIAL: keys share the tenant velocity │ ceiling, but each carries its own budget └── 5. Expensive-service targeting ................... bounded by budget, not by call count
Worst case with production defaults: $10 per tenant-minute, then a 60s cooldown. See
ECONOMIC_SAFETY.md.
AT-3: Cross-tenant compromise
GOAL: read or affect another tenant
├── 1. Guess another tenant's decision/principal id ... blocked: every query filters on caller tenant
├── 2. Trip another tenant's breaker .................. blocked: containment is tenant-scoped
├── 3. Exhaust shared capacity ........................ POSSIBLE: single node, shared workers (D-2/D-3)
└── 4. Cross-tenant read via a product service ........ OUT OF SCOPE of the fabric — each of the 99
services owns its own tenancy; the fabric
does not retrofit isolation onto them
AT-3.4 is the most important residual in this document. The Fabric enforces tenancy at the
boundary and in its own stores. It does not guarantee that every downstream service is internally
multi-tenant-safe.
6. Red-team simulation vectors
Runnable now (assurance/stress_fabric.py, tests/test_economic_safety.py):
| Vector | Implemented as | Result |
| Unbounded self-propagating call chain | scenario_runaway_recursion | Terminated at 24 calls |
| Depth-header forgery | test_depth_is_cooperative_and_the_docs_say_so | Evades depth control by design; caught by chain budget |
| Chain-id rotation | scenario_chain_id_rotation | Falls through to tenant breaker |
| Parallel budget race | scenario_spend_cap_concurrency | 4 of 40 granted, $0 overspend |
| SQL metacharacters in chain id | test_forged_chain_ids_are_sanitised_not_trusted | Sanitised; table intact |
| Storage failure (fail-open probe) | test_containment_fails_closed_when_its_store_is_unavailable | Fails closed |
| Cross-tenant read | 3 tenant-scoping tests | 404, no existence disclosure |
| Agent privilege escalation | 2 tests | 403 |
| Signature forgery / message tamper | test_real_ed25519_signature_verification | Rejected |
Not yet built (named as gaps): prompt-injection corpus replay against the live decision path;
malicious upstream-response handling; MCP protocol fuzzing against the hosted MCP server;
dependency-confusion and supply-chain simulation.
7. Residual risk register
| ID | Residual risk | Likelihood | Impact | Compensating control | Owner decision needed |
| R-1 | Decision store is not tamper-evident | Low | High | Signed-trail products exist separately | Whether to sign Fabric decisions by default |
| R-2 | Enforcement off by default (shadow) | High | High | Dry-run shows exactly what would break; entitlement/rate-limit always enforced | Per-tenant enforcement rollout |
| R-3 | Downstream services not guaranteed multi-tenant-safe | Medium | High | Fabric tenancy at the boundary; per-service audits exist for some | Service-by-service tenancy audit |
| R-4 | Single node, no redundancy | Medium | High | Restart policies, healthchecks, self-heal timer | Multi-node investment |
| R-5 | Chain depth is caller-declared | High | Low | Server-side chain budget + velocity breaker | Accepted |
| R-6 | No pseudonymisation/rehydration pipeline | — | Medium | One-way redaction only | Build or stop implying it |
| R-7 | Host compromise defeats all boundaries | Low | Critical | Standard host hardening only | Out of platform scope |
| R-8 | Upstream tool responses are not screened | Medium | Medium | Request-side screening only | Build response-side screening |
| R-9 | No hosted model/tool pinning | Medium | Medium | DriftGuard covers self-hosted MCP tools | Superlayer Phase 4 |
8. What this document deliberately does not claim
- No certification of any kind is held (SOC 2, ISO 27001, FedRAMP, HIPAA). See
AUDIT_READINESS.md
for control-by-control readiness rather than a compliance claim.
- No penetration test by an external party has been performed on this system.
- No formal guarantee beyond what each individual verification service proves within its own
documented scope.
- This model covers the Trust Fabric boundary and its own stores. It is not a threat model of
all 99 downstream product services individually.