CONTINUITY: Why Composing Individually Sound Security Controls Still Fails — and What It Means for Codex CLI Harness Design
CONTINUITY: Why Composing Individually Sound Security Controls Still Fails — and What It Means for Codex CLI Harness Design
The Composition Problem Nobody Talks About
The dominant conversation around agentic security focuses on individual controls: PreToolUse hooks that block dangerous commands, policy gateways that validate intent, provenance tracking that records data origins. Each mechanism is evaluated in isolation, and each can be made individually sound. The tacit assumption is that stacking correct controls produces a correct system.
Chris Zheng and Geng Yang of ZAST.AI challenged this assumption directly in their September 2026 paper, CONTINUITY: Security-Context Contracts for Composable LLM Agent Controls (arXiv:2609.05269).1 Their central finding is stark: individually correct security mechanisms do not necessarily compose into an end-to-end secure system. They give this failure class a name — security-context discontinuity — and demonstrate it experimentally across 2,560 parameterised attack instances.
The finding matters to any team operating Codex CLI in production, where a typical harness stacks AGENTS.md policy declarations, PreToolUse hooks, approval policies, MCP tool scopes, and Guardian review in sequence. Each component is configured carefully; the composition is rarely audited.
What Security-Context Discontinuity Is
CONTINUITY defines discontinuity with precision: a fact required to justify a realised external effect is (i) absent, (ii) weakened, (iii) reinterpreted under a non-equivalent schema, (iv) modified without an authorised relation, or (v) no longer fresh at the point of effect.1
The paper identifies four operators through which discontinuity occurs in practice:
- Truncation — a downstream component drops a required security field (e.g., a provenance label stripped by a protocol adapter)
- Amplification — authority increases without a separately authenticated grant (e.g., a gateway approves
scope:readbut an adapter silently escalates toscope:write) - Rebinding — an approval intended for one principal, field, or value is reused for another (e.g., an alias resolver maps
merchant:9tobankacct:attackerafter gateway validation) - Staleness/Replay — valid credentials used after expiry, revocation, or policy epoch change
The minimal counterexample in the paper is instructive. A policy gateway approves a payment transfer of £5,000 to alias:merchant:9 from a verified invoice. A downstream adapter resolves the alias but outputs bankacct:attacker. The finality sink authenticates the adapter output but cannot verify the alias mapping. Every individual component behaved correctly; the composed pipeline transferred funds to an attacker.1
The CONTINUITY Model
Assume-Guarantee Contracts
CONTINUITY models each pipeline component as a contract C_i = (A_i, G_i, P_i, M_i) where:1
- A_i — input assumptions: upstream guarantees this component requires
- G_i — output guarantees: conditions this component establishes on success
- P_i — preserved field paths: security-critical values that must not change across the component
- M_i — transformation relations: explicit, independently witnessed mappings for legitimate field changes
Seven critical fields are tracked throughout the pipeline: principal identity, task root, provenance manifest digest, delegation scope, current policy epoch and digest, structured action representation, and finality state.
Six Control Elements
The reference implementation enforces end-to-end integrity via six concrete mechanisms:1
- Signed root grants (Γ) — bind principal, agent actor, task, authority set, delegation scope, permitted tools and servers, data classification, policy identity, and constraints; verified by a separately trusted root issuer
- Provenance commitments — field-level source bindings with leaf-path resolution and value digests for every claimed data origin
- Role-bound transition receipts (ρ_i) — each pipeline stage signs its output paired with input digest, contract digest, change set, and assumption/guarantee predicate identifiers
- Bounded typed releases (L) — source identity, exact value digest, target path, bounded predicate, operation, tool, task, and expiry binding; prevents value substitution or predicate-only whitelisting
- Transformation witnesses (w) — independently trusted signatures validating before/after value pairs satisfy declared relations (e.g., alias resolution must be attested by a trusted directory issuer)
- Effect-bound execution permits (π) — issued only after complete bundle verification; revalidates subject, action, policy, revocation, expiry, nonce, and idempotency at the finality sink
flowchart LR
subgraph Pipeline["Composable Agent Pipeline"]
direction LR
Root["Root Grant (Γ)"]
Memory["Memory Stage\nC_mem=(A,G,P,M)"]
Gateway["Gateway Stage\nC_gw=(A,G,P,M)"]
Adapter["Adapter Stage\nC_ad=(A,G,P,M)"]
Finality["Finality Sink\nPermit (π)"]
end
Root -->|"signed root grant"| Memory
Memory -->|"receipt ρ₁\n+ provenance digest"| Gateway
Gateway -->|"receipt ρ₂\n+ policy-authorized"| Adapter
Adapter -->|"receipt ρ₃\n+ transform witness (w)"| Finality
Finality -->|"effect-bound permit\nor ESCALATE"| Effect["External Effect"]
style Root fill:#1a3a4a,color:#e0f0ff,stroke:#4a9aba
style Memory fill:#1a3a2a,color:#e0ffe0,stroke:#4a9a6a
style Gateway fill:#3a2a1a,color:#fff0e0,stroke:#9a6a4a
style Adapter fill:#2a1a3a,color:#f0e0ff,stroke:#6a4a9a
style Finality fill:#3a1a1a,color:#ffe0e0,stroke:#9a4a4a
style Effect fill:#2a2a2a,color:#ffffff,stroke:#888888
Formal Composition Safety
The paper proves four theorems that bound what compositions can and cannot guarantee.1 The key result (Theorem 1) is that if seven conditions hold — authenticated origin, contract compatibility, context continuity, non-amplification, canonical action binding, fresh finality, and complete mediation — then every realised protected effect has a valid effect witness. Theorems 2–4 prove the converses: truncation, unbound-field substitution, and authority laundering are each sufficient to break end-to-end security regardless of individual component correctness.
Experimental Results
The reference implementation2 was evaluated against 2,560 parameterised attack instances spanning 128 fault-domain classes (32 fault classes across four application domains — workspace email, finance, DevOps, and delegation — at 20 instances per fault-domain class).1
| Configuration | Attack Success Rate |
|---|---|
| CONTINUITY (full) | 0.0% |
| Gateway + Finality (best incomplete) | 65.6% |
| Effect-Bound Permit only | 81.3% |
| No controls | 100.0% |
All 700 benign tasks auto-completed (100%). All 200 tasks involving unreleased external fields escalated to human review (100%). No false positives on any successfully released task.
Ablation by mechanism reveals which components carry most weight:1
| Removed component | Fault classes reopened |
|---|---|
| Field provenance | 24 of 32 |
| Contract conformance | 24 of 32 |
| Incomplete mediation | 24 of 32 |
| Root authentication | 16 of 32 |
| Release validation | 12 of 32 |
Performance overhead is modest: median proof verification at 4.21 ms, median end-to-end transition and finality at 7.17 ms.1 Bundle size grows linearly from ~8.1 KB for a single-transition chain to 49.4 KB at 20 transitions.
What This Means for Codex CLI
Codex CLI’s security architecture composes multiple mechanisms across an execution pipeline. Mapping them to CONTINUITY’s model surfaces gaps that individual audits miss.
The Codex CLI Pipeline as a Composition
flowchart TD
subgraph CodexPipeline["Codex CLI Execution Pipeline"]
AGENTS["AGENTS.md\n(policy epoch / P_i declarations)"]
PRE["PreToolUse hook\n(Gateway analogue: G_i = policy-authorized)"]
EXEC["Tool Execution\n(action semantics)"]
POST["PostToolUse hook\n(Finality analogue: revalidate + replace)"]
GUARDIAN["Guardian LLM Reviewer\n(semantic adjudication)"]
end
UserInput["User Prompt\n(principal, task root)"] --> AGENTS
AGENTS --> PRE
PRE -->|"exit 0: proceed"| EXEC
PRE -->|"exit 2: block"| Block["Blocked"]
EXEC --> POST
POST -->|"replace result"| Model["Model sees curated output"]
POST -->|"exit 2"| Stop["Turn stopped"]
EXEC -->|"on_mcp_tool_result"| GUARDIAN
GUARDIAN --> Model
style UserInput fill:#1a3a4a,color:#e0f0ff,stroke:#4a9aba
style AGENTS fill:#3a2a1a,color:#fff0e0,stroke:#9a6a4a
style PRE fill:#1a3a2a,color:#e0ffe0,stroke:#4a9a6a
style EXEC fill:#2a2a3a,color:#e0e0ff,stroke:#6a6a9a
style POST fill:#2a1a3a,color:#f0e0ff,stroke:#6a4a9a
style GUARDIAN fill:#3a1a1a,color:#ffe0e0,stroke:#9a4a4a
Where Discontinuity Can Occur
Truncation in MCP adapters: When a Codex CLI hook approves a tool call via on_mcp_tool_result, the provenance of the data returned by the MCP server is not propagated to subsequent hook invocations. A downstream PostToolUse hook operates on the model-visible result without any binding to the upstream grant that authorised the request. This is a provenance truncation: the gateway decision and the finality check share no authenticated link.
Rebinding across hook chain ordering: Hook chain ordering in Codex CLI is explicit — the first handler to return a deny wins; later handlers in the same matcher group do not execute.3 This means that an approval returned by hook A is silently consumed as authorisation for the full action, including field values that hook B would have validated. If hook B is responsible for validating destination fields and hook A ran first and returned {decision: "approve"}, hook B never executes. This is structural rebinding: the approval is reused across a broader scope than the approving control examined.
Policy epoch staleness: AGENTS.md is read once at session start.3 If a policy is updated mid-session (e.g., a new writable_roots constraint pushed to a shared .codex/config.toml), running tool calls continue under the stale epoch. CONTINUITY would require either session invalidation on policy change or explicit epoch binding in each permit.
Amplification through Guardian fallback: Guardian LLM review is invoked only when approval policy requires human confirmation.3 For tool calls operating under --approve-for-me, the Guardian acts as autonomous approver. If Guardian approves based on the task description but not the specific parameter values (a semantic gap), the approval covers a wider scope than the described action — a form of amplification.
Practical Mitigations
CONTINUITY’s framework suggests four concrete patterns for Codex CLI configurations:
1. Provenance-binding PostToolUse hooks
Every MCP tool result that feeds a subsequent write should carry a source annotation the PostToolUse hook can validate:
# .codex/config.toml
[[hooks.post_tool_use]]
matcher = { tool = "mcp__*" }
command = "python .codex/hooks/verify_provenance.py"
# .codex/hooks/verify_provenance.py
# Reads tool result from stdin (JSON), checks source field against allowlist
import json, sys, os
result = json.load(sys.stdin)
allowed_origins = os.environ.get("ALLOWED_ORIGINS", "").split(",")
source = result.get("_source", "unknown")
if source not in allowed_origins:
print(json.dumps({"decision": "block",
"reason": f"Unbound source: {source}"}))
sys.exit(0)
2. Explicit field-level approval in PreToolUse
Rather than approving a tool call holistically, validate the specific field values that will have external effect:
[[hooks.pre_tool_use]]
matcher = { tool = "bash", command_prefix = "curl" }
command = "python .codex/hooks/validate_destination.py"
# Hook reads {tool_input: {cmd: "..."}} from stdin,
# extracts URL, checks against scoped allowlist,
# returns {decision: "block"} if destination not in grant scope
3. Policy epoch pinning in AGENTS.md
## Security Policy — Epoch: 2026-09-08T00:00:00Z
<!-- POLICY_EPOCH: 2026-09-08 -->
All tool calls are governed by this policy version.
If this file has been modified since session start, stop and request re-authorisation.
A PreToolUse hook can read the epoch header and compare it against the session-start timestamp recorded in the environment.
4. Scope-bounded approval profiles
Use named permission profiles to bind approval scope to explicit tool sets, preventing amplification through profile inheritance:
[profile.code-review]
approval_policy = "on-failure"
writable_roots = []
allowed_tools = ["read_file", "glob", "grep"]
# Explicit scope: reading only; Guardian cannot approve writes under this profile
Limitations and Open Problems
The CONTINUITY paper acknowledges several constraints that apply directly to any Codex CLI adaptation.1 Trusted roots are deployment configuration inputs — there is no auto-provisioning, so the root grant issuer must be maintained as a separate trusted component. The verifier validates structural integrity, not semantic correctness of trusted validators; a malicious trusted directory issuer could sign a fraudulent alias mapping and pass all CONTINUITY checks. The synthetic benchmark is not sampled from real-world attack distributions, so the 0.0% ASR figure should be understood as a guarantee over the 128 enumerated fault classes, not a universal security proof.
The SoK paper on multi-agent LLM security (arXiv:2609.00595)4 contextualises CONTINUITY’s contribution: across 197 surveyed works, the hardest open problems are path closure and recovery — precisely the scenarios where a composition break allows an attack to complete before any individual control fires. CONTINUITY addresses path closure but does not yet provide recovery mechanisms for partially executed pipelines.
Summary
CONTINUITY (arXiv:2609.05269) demonstrates that security-context discontinuity is not a marginal concern — the best incomplete baseline (Gateway + Finality without assume-guarantee contracts) still allows 65.6% of attacks to succeed. For Codex CLI operators, the practical implication is that auditing each hook, policy, and approval setting in isolation is insufficient. The composition — the gaps between hooks, the provenance bindings that do not cross stage boundaries, the approval scopes that expand silently — is where production attacks will land. Structured provenance-binding hooks, field-level approval validation, explicit policy epoch pinning, and scope-bounded profiles each address one of the four discontinuity operators. Applying all four moves closer to the CONTINUITY guarantee: every realised effect is backed by a valid, current, scoped, and non-amplified authorisation witness.
Citations
-
Zheng, C. & Yang, G. (2026, September 4). CONTINUITY: Security-Context Contracts for Composable LLM Agent Controls. arXiv:2609.05269. https://arxiv.org/abs/2609.05269 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10
-
ZAST.AI. (2026). CONTINUITY Reference Implementation. GitHub. https://github.com/zast-ai/continuity ↩
-
OpenAI. (2026). Codex CLI Hooks: Complete Guide to Events, Policy Engines and Production Patterns. Codex Knowledge Base. https://codex.danielvaughan.com/2026/04/15/codex-cli-hooks-complete-guide-events-policy-patterns/ ↩ ↩2 ↩3
-
Yang, R., Xu, J., Liu, Z., Fendley, N., Hong, Y., Li, Z., & Cao, Y. (2026, September 1). SoK: When Safe Agents Fail Together: The Security of Multi Agent LLM Systems. arXiv:2609.00595. https://arxiv.org/abs/2609.00595 ↩