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

#ThreatCategorySurfaceStatusControl / evidence
S-1Forged tenant identity via a guessed/stolen API keySpoofingTB-2MitigatedLive entitlement check per request, fail-closed; keys stored only as SHA-256 fingerprints in the Fabric registry (fabric_control_plane.py::_fingerprint)
S-2Agent self-asserting a privileged principalSpoofingTB-2MitigatedPrincipals 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-3Forged X-CAIN-Chain-Id / depthSpoofingTB-2Partially mitigated, documentedDepth 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-4Signature forgery on a delegation tokenSpoofingTB-3MitigatedEd25519 via cryptography; verification returns 503 rather than a meaningless true if the library is absent (test_real_ed25519_signature_verification)
T-1Tampering with a decision record after the factTamperingTB-4NOT mitigated — statedThe decision store is durable, not signed or tamper-evident. Cryptographic trails are separate products (QuorumSeal, blizzard-governance signed audit)
T-2SQL injection via chain id / principal idTamperingTB-4MitigatedParameterised statements throughout; chain ids character-restricted (test_forged_chain_ids_are_sanitised_not_trusted passes '; DROP TABLE ...)
T-3Poisoned MCP tool definition changing under the callerTamperingTB-1/TB-3Mitigated (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-4Malicious tool *output* steering the next agent stepTamperingTB-1Partially mitigatedInjection screen runs on request payloads; talos-shield can screen text; there is no mandatory screening of upstream responses on the hosted path
R-1Tenant denies having authorised an actionRepudiationTB-4Mitigated for decisionsEvery 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-2Operator changes policy silently, then denies itRepudiationTB-3MitigatedPolicy version is sha256 over the rules OPA actually serves; a silent change changes the id (test_policy_version_changes_when_the_rules_change)
I-1Cross-tenant data disclosureInfo disclosureTB-2/TB-4MitigatedEvery 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-2Secrets in logs or URLsInfo disclosureTB-2MitigatedKeys 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 proxyInfo disclosure / EoPTB-2MitigatedWhole namespace blocked centrally at the gateway before auth is even considered
I-4PII leaving via an agent payloadInfo disclosureTB-1Partially mitigatedpii-redactor, LeakGuard (self-hosted) exist as services but are not mandatory in the call path
D-1Runaway agent exhausting capacity/costDoSTB-2MitigatedChain depth, per-chain call budget, tenant velocity breaker; proven live to terminate an unbounded chain in 24 calls
D-2Single-node failure taking the platform downDoSTB-2/TB-3NOT mitigated — statedOne VM, no multi-node redundancy. See RELIABILITY_SRE.md
D-3SQLite write contention under loadDoSTB-4Partially mitigatedWAL, per-process connections, amortised pruning; measured ceiling ~83 rps at concurrency 24
E-1Agent sub-key escalating to parent privilegesEoPTB-2MitigatedAgent keys cannot mint principals, revoke principals, or mint delegation tokens; tier and budget inherited, never elevated
E-2Budget bypass by parallelismEoP (economic)TB-2/TB-4Mitigated (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-3Confused deputy — gateway using its own authority for a callerEoPTB-2/TB-3MitigatedActionProof is called with the *caller's* key, not a gateway credential; the gateway holds no shared secret for it

3. LINDDUN (privacy)

#ThreatStatusNotes
L-1Linkability of calls to a personPartialDecisions 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-5Identifiability from stored payloadsMitigatedDecision records store service, path, verdicts — not request bodies
N-1Non-repudiation used against a data subjectAcceptedEvidence exists by design; retention is bounded (default 90 days) and configurable
D-4Detectability of whether a principal existsMitigatedCross-tenant lookups return 404, not 403 — existence is not disclosed
D-5Disclosure of informationSee I-1…I-4
U-1Unawareness — data subjects unaware of processingPartial/privacy states processing; content-provenance issues EU AI Act Art. 50 disclosure manifests. No per-data-subject notice mechanism
N-2Non-compliance with stated policyMitigatedRetention 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:

TechniqueIDRelevanceControl
Valid AccountsT1078Stolen API keyLive entitlement check; per-agent revocation; key rotation (/subscribe/rotate-key)
Exploit Public-Facing ApplicationT1190The gateway is the public surfaceSingle entry point; /internal/* block; security headers; CSP
Command and Scripting InterpreterT1059Agent-driven code executionHosted: no general code execution primitive is exposed. Self-hosted: ToolWarden sandbox
Server Software ComponentT1505Malicious MCP toolDriftGuard pinning, MCP Security Scanner (self-hosted)
Unsecured CredentialsT1552Keys in logs/URLsFingerprint-only storage; header-only transport
Automated CollectionT1119Agent mass-reading tenant dataTenant scoping; velocity breaker
Resource HijackingT1496Runaway/abusive compute or spendContainment + atomic spend caps
Data from Information RepositoriesT1213Cross-tenant readTenant-scoped queries throughout
Impair DefensesT1562Disabling the gateEnforcement 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):

VectorImplemented asResult
Unbounded self-propagating call chainscenario_runaway_recursionTerminated at 24 calls
Depth-header forgerytest_depth_is_cooperative_and_the_docs_say_soEvades depth control by design; caught by chain budget
Chain-id rotationscenario_chain_id_rotationFalls through to tenant breaker
Parallel budget racescenario_spend_cap_concurrency4 of 40 granted, $0 overspend
SQL metacharacters in chain idtest_forged_chain_ids_are_sanitised_not_trustedSanitised; table intact
Storage failure (fail-open probe)test_containment_fails_closed_when_its_store_is_unavailableFails closed
Cross-tenant read3 tenant-scoping tests404, no existence disclosure
Agent privilege escalation2 tests403
Signature forgery / message tampertest_real_ed25519_signature_verificationRejected

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

IDResidual riskLikelihoodImpactCompensating controlOwner decision needed
R-1Decision store is not tamper-evidentLowHighSigned-trail products exist separatelyWhether to sign Fabric decisions by default
R-2Enforcement off by default (shadow)HighHighDry-run shows exactly what would break; entitlement/rate-limit always enforcedPer-tenant enforcement rollout
R-3Downstream services not guaranteed multi-tenant-safeMediumHighFabric tenancy at the boundary; per-service audits exist for someService-by-service tenancy audit
R-4Single node, no redundancyMediumHighRestart policies, healthchecks, self-heal timerMulti-node investment
R-5Chain depth is caller-declaredHighLowServer-side chain budget + velocity breakerAccepted
R-6No pseudonymisation/rehydration pipelineMediumOne-way redaction onlyBuild or stop implying it
R-7Host compromise defeats all boundariesLowCriticalStandard host hardening onlyOut of platform scope
R-8Upstream tool responses are not screenedMediumMediumRequest-side screening onlyBuild response-side screening
R-9No hosted model/tool pinningMediumMediumDriftGuard covers self-hosted MCP toolsSuperlayer Phase 4

8. What this document deliberately does not claim

for control-by-control readiness rather than a compliance claim.

documented scope.

all 99 downstream product services individually.

Back to security & disclosure →