# IHVRK threat model

Scope: the Independent Human Verification Resource Kit (`ihvrk/`): the boundary between an autonomous agent that
*proposes* a high-impact action and the component that *executes* it. CAIN-42 moat placement: Moat 2 (security-context
continuity: authority bound across the agent → human → executor boundary) and Moat 4 (execution provenance: decision ↔
execution ↔ evidence). It is not a new moat.

## 1. Assets

| Asset | Why it matters |
|---|---|
| A1 The protected operation (e.g. a payment, a deploy) | the thing an attacker wants to run |
| A2 Verifier private keys (held on verifier devices) | sign approvals |
| A3 Enrollment-authority key | decides who is a verifier / break-glass operator |
| A4 Permit-issuer key, challenge key, checkpoint key | sign permits, challenges, checkpoints |
| A5 Registry DB (`ihvrk.db`) | nonce, request and permit state: single-use guarantees live here |
| A6 Evidence ledger + checkpoints | what an auditor reconstructs |
| A7 Policy version, trusted clock, attestation config | inputs to every decision |

## 2. Trust boundaries

```
 UNTRUSTED                         | IHVRK SERVICE (trusted computing base)                 | EXECUTION SIDE
                                   |                                                         |
 agent / LLM / prompts / tools ----A--> propose(): canonicalize, digest, epoch, challenge --G--+ admin: enrollment
 retrieved docs / tool output      |        |                                                |   authority, status
                                   |        v                                                |   lists, policy ver.
 network (attacker-controlled) ----B--> submit_approval(): authn -> nonce CAS -> binding     |
   ^                               |        |  (verifier registry, credential status)        |
   |  human verifier device        |        v                                                |
   +-- (outside: device, person) --+   C: policy (Python)  ==  Z3 model  (both must agree)    |
                                   |        |                                                |
                                   |        v   D: permit signed (keystore)                  |
                                   |   ExecutionGate.execute(): verify, re-digest,       --E--> protected operation
                                   |   consume permit (one DB transaction)               |   (only reachable via gate)
                                   |        |                                                |
                                   |        v   F: append-only ledger --> signed checkpoint --+--> witness / auditor
 Linux host: agent cgroup  <--------------- E': talos_gatekeeper (cgroup.freeze), eBPF LSM   |   (off-host)
```

| Boundary | Trusted | Partially trusted | Untrusted | Outside the assurance boundary |
|---|---|---|---|---|
| A proposal | canonical encoder | – | agent, prompts, tool outputs, action text | – |
| B human identity & approval | verifier registry, status list (signed by enrollment authority) | verifier device (holds key) | network, message contents until verified | whether the person understood the action; identity proofing done by the enrollment authority |
| C policy | `authorization_policy.py` + Z3 model | – | any value derived from the request | correctness of Z3 itself |
| D signing | keystore (SOFTWARE_TEST here) | – | – | HSM (not present) |
| E enforcement | `ExecutionGate`, registry transaction | gatekeeper script (needs root) | agent process | any route to the resource that bypasses the gate |
| F evidence | ledger writer, checkpoint key | local filesystem | – | the witness location (deployment-provided) |
| G admin & recovery | enrollment authority, operator running `reconcile` | – | – | key ceremony, staffing |

## 3. Attacker capabilities assumed

The attacker may: control one or more agents and everything they read or emit; act as an API client; read, drop,
delay, duplicate, reorder and replay any network message; inject arbitrary unauthenticated messages; control the
wall clock *of hosts other than the service* (the service's clock is an assumption, A-TIME); compromise a subset of
replicas only where a quorum is configured (IHVRK itself uses one registry; see L-STORE); run as an ordinary
unprivileged process in the agent's cgroup.

## 4. Stronger compromises (analysed separately)

| Scenario | Effect | IHVRK response |
|---|---|---|
| Root / kernel compromise of the service host | can edit DB, ledger, keys, code | **cannot prevent**. Detects ledger rewrite/truncation only if a checkpoint is held off-host (`ROLLBACK`, `CHECKPOINT_MISMATCH`). |
| HSM compromise | permits forgeable | not applicable today: there is no HSM (L-HSM). With the software keystore, compromise of the service host = compromise of keys. |
| Verifier device compromise | attacker signs valid approvals for that verifier | **cannot prevent** the approval. Contains: approval is bound to one action digest, one request, one nonce, short expiry; revocation via status list or `revoke_locally`. Detects: every approval is in the ledger under the verifier id. |
| Collusion of independent humans | two break-glass operators can force one action | by design (two-person rule). Bound to one digest; notifier must succeed; logged as `BREAK_GLASS_AUTHORIZED` + `SECURITY_ALERT`. |
| Enrollment-authority key compromise | attacker can enroll a verifier | **cannot prevent**. Detects: enrollment records are signed and kept; KID_REUSE stops silent key substitution of existing verifiers. |
| TEE / attestation infrastructure compromise | false environment claims | hardware appraisal not implemented; HARDWARE_BACKED claims are never accepted as hardware evidence (always INDETERMINATE). |

## 5. Attack tree (goal: run action X without a valid human approval of X)

```
G0 execute X without authorized human approval of X
├─ G1 obtain a permit for X
│  ├─ G1.1 forge an approval ............... Ed25519 + enrolled key (A1-A4, B1)            PREVENTED
│  ├─ G1.2 agent self-approval ............. agent ids/keys never enrollable; independence (A1, A5, E1, E7)  PREVENTED
│  ├─ G1.3 replay an old approval .......... nonce CAS, request state CAS (B2, D6, D8, D9)  PREVENTED
│  ├─ G1.4 approval for Y used for X ....... digest binding at approval AND at gate (C2-C4)  PREVENTED
│  ├─ G1.5 make the policy/solver say yes .. two encodings must agree; UNKNOWN/TIMEOUT block (H1, H2, H9)  PREVENTED
│  ├─ G1.6 stale identity / time ........... status list freshness, clock uncertainty window (A8, A9, H3, H4)  PREVENTED
│  ├─ G1.7 abuse break-glass ............... 2 distinct operators, role, binding, notifier (E4-E6)  PREVENTED (collusion: see §4)
│  └─ G1.8 forge a permit .................. pinned permit key (E2)                         PREVENTED
├─ G2 run X with someone else's permit ..... principal/tenant/resource/audience/digest at gate (D2, D11)  PREVENTED
├─ G3 run X twice with one permit .......... single DB transaction consume (D1, D3, I9)      PREVENTED
├─ G4 run X before/after revocation ........ consume is the final check (integration tests)  PREVENTED before consume; NOT after (by design, §ARCHITECTURE TOCTOU)
├─ G5 bypass the gate entirely ............. direct access to the resource                    OUTSIDE BOUNDARY (deployment must make the gate the only path)
├─ G6 act while frozen / blocked
│  ├─ G6.1 processes in the agent cgroup ... cgroup.freeze (F1-F4)                           CONTAINED
│  ├─ G6.2 helper outside the cgroup ....... (F12 asserts it keeps running)                  NOT CONTAINED (residual)
│  ├─ G6.3 already-open sockets / queued I/O frozen tasks issue nothing new; in-flight kernel I/O completes  RESIDUAL
│  └─ G6.4 kill the enforcer ............... cgroup stays frozen (F6)                          CONTAINED
└─ G7 hide that it happened ................ ledger chain + off-host checkpoint (G1-G14)       DETECTED (with checkpoint)
```

## 6. Risk register

| ID | Risk | L | I | Control | Residual |
|---|---|---|---|---|---|
| R1 | Verifier key stolen | M | H | binding, expiry, revocation, ledger | H (approval of shown action still possible) |
| R2 | Human approves without understanding | M | H | exact action digest + human-readable rendering is the verifier app's job | H, outside IHVRK |
| R3 | Resource reachable without the gate | M | H | deployment requirement; eBPF LSM design | H until integrated |
| R4 | Service host root compromise | L | H | off-host checkpoints | H |
| R5 | Single SQLite file = single node | H (for HA needs) | M | documented; blocks multi-node use | M (L-STORE) |
| R6 | Clock wrong on service host | L | H | declared uncertainty; uncertain boundary blocks | M (A-TIME) |
| R7 | DoS: flooding proposals / bad approvals | M | M | per-agent and global pending caps, MAX_ATTEMPTS, size caps | M (an attacker who can submit approvals can deny a pending request with 5 forgeries) |
| R8 | Crash after consume, before outcome | L | M | request stays EXECUTING → reconcile → INDETERMINATE, never retried | L |
| R9 | Software keys described as HSM | L | M | protection class label; HardwareKeyStore has no fallback | L |
| R10 | Z3 / Python policy share a bug | L | H | independently written; differential test; TLA+ model | M |

L/I = likelihood / impact (qualitative, author's estimate, not measured).

## 7. Threat → control → test matrix

| Threat | Requirement | Invariant | Implementation | Tests |
|---|---|---|---|---|
| Synthetic approval (A) | only enrolled human keys approve | I2, I3, I14 | `verification_service.submit_approval` stage 1, `verifier_registry` | A1–A12, E1, E7 |
| Deepfake/telemetry (B) | biometrics are not evidence; only the signed approval counts | I2, I14 | unsigned fields rejected; `authn.aal` capped by enrollment | B1–B4 |
| Prompt injection (C) | proposal content is data | I7 | proposal only hashed; policy/registry not reachable from request | C1–C4 |
| Replay & races (D) | single use | I5, I11, I15 | `epoch_registry` CAS transactions | D1–D11, I2, I9 |
| Collusion (E) | agents ≠ verifiers; 2-person break-glass | I14 | registry independence, `break_glass.py` | E1–E7 |
| Escape (F) | agent blocked while pending | – (runtime) | `talos_gatekeeper.sh`, eBPF LSM | F1–F12 |
| Ledger tampering (G) | tamper-evident evidence | – (ledger) | `append_only_ledger`, `ledger_verifier`, checkpoints | G1–G14 |
| Solver/policy failure (H) | unresolved never authorizes | I10, I13 | `ihvrk_solver.decide` | H1–H9, formal suite |
| Recovery (I) | no fail-open after crash | I1, I15 | `reconcile`, gate | I1–I10 |

## 8. Findings from peer audits of the existing CAIN paths (session -17, 2026-10-12)

These concern existing CAIN code, not `ihvrk/`. Recorded so the IHVRK integration plan addresses them.

| ID | Finding | Relevance to IHVRK |
|---|---|---|
| T-S17-03 (blocker) | `cain.guard` falls back to the local allowlist when the gateway is unreachable; `CAIN_ALLOW_OFFLINE_LOCAL` defaults true, so `fail_closed=True` is ignored | the IHVRK gate has no offline-allow path; integration must not route through that fallback |
| T-S17-04 | SDK trusts and caches an unsigned gateway ALLOW | IHVRK permits are signed and verified at the gate; integration must carry the permit, not a bare ALLOW |
| T-S17-05 (blocker) | `cain-ebpf/cain_enforce.bpf.c` is tracepoint-only and returns 0: telemetry, not enforcement; BPF LSM not active on this host | same conclusion here: `ihvrk_telemetry.bpf.c` is labelled telemetry; `ihvrk_lsm.bpf.c` enforces but cannot attach on this host |
| T-S17-06 | MCPGate writes no durable per-request ledger | candidate consumer of `ihvrk/ledger` (see INTEGRATION.md) |
