Skip to content

Revocable Resource-and-Effect Capabilities for Coding Agents (PORTICO)

Materialise each subgoal-scoped capability as a revocable epoch-bound handle — closure removes it from the planner and stale replay is rejected.

When this recommendation applies

The pattern produces a real security gain only under these conditions (Santos-Grueiro, 2026):

  • All effectful tool traffic is mediated — every write, exec, and egress call goes through the reference monitor. Raw shellouts, embedded SDK calls, and cached state bypass it.
  • The tool catalog is typed and sound — closure predicates and deny rules are written against named effects. A wildcard bash tool makes every handle a wildcard handle, so the closure check has nothing concrete to gate.
  • Subgoals are long enough that stale replay is a real risk — the lifecycle pays for itself only when a capability outlives the work that justified it. Short single-subgoal sessions are already bounded by transport-level TTLs.
  • The task contract is hand-authored or vetted — if the planner auto-generates closure predicates from the user's natural-language prompt, the predicates inherit the planner's mistakes.

Outside these conditions, short-TTL transport-level credentials plus task-scope-security-boundary close the same gap with less authorship cost. See when this backfires.

What lingering authority is

A coding agent receives broad tool access for an entire task even when a resource is needed for only one subgoal. "Run the migration" grants write access to the production database for the migration step. Once the migration returns, the planner can still call db.write(...) for the rest of the session — including in a later turn whose justification an attacker controls. The capability lingers past the episode that authorized it (Santos-Grueiro, 2026).

This differs from permission-laundering across composition: there the combination of legal calls produces an illegal effect, while here a single previously-legal call becomes illegal because the work that justified it has closed. It also differs from authority confusion: there the issuer is the wrong principal, while here the issuer is correct but the window has expired.

How the request-grant-invoke lifecycle works

PORTICO compiles an explicit task contract into four artifacts: initial capabilities, grant rules, trusted closure predicates, and global deny rules. Any expansion beyond the initial set becomes an opaque, epoch-bound handle through three phases (Santos-Grueiro, 2026):

Phase What happens Failure mode it closes
Request The planner asks the monitor for a capability that the contract's grant rules permit Capability creation outside the contract
Grant The monitor issues an opaque, epoch-bound handle; the planner never sees the underlying token Handle forgery, capability re-derivation from the planner side
Invoke Each tool call dereferences the handle through the monitor; a global deny rule may still reject it Composition that the contract forbids even with valid handles
Close A trusted closure predicate fires; the handle leaves the next planner interface and any later invoke is rejected as stale replay Lingering authority — replay after the justifying subgoal has closed
graph TD
    P[Planner]
    P -->|1 request| M[Reference monitor<br/>contract, grant rules,<br/>closure predicates,<br/>deny rules]
    M -->|2 grant epoch-bound handle| P
    P -->|3 invoke handle| M
    M -->|4 deny check passes| T[Tool effect]
    M -->|closure predicate fires| X[Handle removed from<br/>next planner interface]
    P -.->|5 stale replay attempt| M
    M -.->|reject before side effect| R[Denied]

    style M fill:#fbca04
    style X fill:#0e8a16,color:#fff
    style R fill:#b60205,color:#fff

The closure step carries the weight. Once the handle leaves the next planner interface, the planner cannot name the capability to replay it, even under prompt injection. Replay is structurally impossible, not merely policy-discouraged (Santos-Grueiro, 2026).

Why it works

The lifecycle moves the enforcement boundary from "per-tool call" to "per-subgoal handle lifetime" — the layer at which lingering authority accrues. The reference-monitor design then holds regardless of model behavior: the planner cannot replay a capability whose handle it can no longer name. This is the same local-enforceability property CaMeL achieves through dual-LLM and a Python interpreter, applied to effect-handles over time rather than values across composition. On real repository layouts with file writes, git mutations, and network egress, the paper reports zero executed contract-forbidden effects, 10/10 post-closure reuses rejected (a non-revoking comparator permitted all 10), and 0/6 versus 6/6 stale-write outcomes (Santos-Grueiro, 2026).

When this backfires

The mechanism degrades or fails outside its operating envelope.

  • Off-protocol egress bypasses the monitor. Raw shell curl, embedded SDK calls, headless browsers, or cached filesystem state that skips the reference monitor sits outside its reach — the same mediated-control-plane limit that bounds monotonic capability attenuation and the MCP runtime control plane.
  • Untyped tool surfaces collapse the closure check. Closure predicates and deny rules are written against a typed catalog of tools and effects. A generic bash tool, a free-form exec, or a wildcard MCP server makes every handle a wildcard handle, so the lifecycle runs but the close step gates nothing concrete. Pattern-based permission rules without typed catalogs are coarse-grained and cannot capture context-sensitive policies (Augment Code, Common Agentic Attack Patterns).
  • Short single-subgoal sessions get no benefit. When the whole agent lifecycle fits inside one subgoal, short-TTL transport-level credentials (OAuth token exchange, fine-grained PATs, SPIFFE/SPIRE SVIDs) already bound the replay window without an in-harness lifecycle. The request-grant-invoke loop adds bookkeeping for no real security gain (Strata, 2026 OAuth Token Exchange & Agentic AI).
  • Naive task-contract authoring inherits planner mistakes. Auto-generating closure predicates from the user's natural-language prompt is the same failure mode as naive manifests in monotonic capability attenuation: the defense reduces to whatever the planner inferred, which is the principal it was meant to constrain. Hand-authored or vetted contracts are a prerequisite, not a nice-to-have.
  • Confused-deputy through the closure predicate itself. If the closure predicate reads untrusted runtime context — "close the handle when this returned object has done=true" — an attacker who controls the data path can keep the handle alive indefinitely. Closure must evaluate against trusted state only.
  • Authorship cost compounds. Capability-based systems carry a well-known implementation cost: someone must enumerate the capability claims that today are implicit (Capability Myths Demolished). PORTICO multiplies this by adding per-subgoal closure predicates on top of the per-tool catalog.

When the envelope does not hold, the pragmatic alternative is short-TTL transport credentials plus task-scope-security-boundary and a closed lethal trifecta leg. These close lingering authority deterministically without a reference monitor in the harness.

How it relates to adjacent patterns

PORTICO, monotonic capability attenuation, and HBHC are three different cuts at the same family of risk:

Pattern What it scopes to Revocation event Authorship cost
Monotonic capability attenuation A value's sink budget across composition None — authority can only shrink through composition Expert-crafted sink budgets per value class
Heartbeat-bound hierarchical credentials A credential across an agent hierarchy Parent stops heartbeating; descendants expire within W_max + Δ_h + ε NTP discipline + parent keys in an enclave
Revocable resource-and-effect capabilities (this page) A capability handle across a subgoal's lifetime Trusted closure predicate fires Hand-authored task contract + typed catalog + closure predicates

The mechanisms compose: value-level attenuation, hierarchy-level liveness, and subgoal-level closure address orthogonal axes. None alone covers the others' failure modes (Santos-Grueiro, 2026).

Key Takeaways

  • PORTICO addresses lingering authority — capabilities that remain exposed after the subgoal that justified them closes — by materialising every expansion as an opaque, epoch-bound handle and removing the handle from the planner's interface at closure.
  • The reported security delta is large in its envelope: zero executed contract-forbidden effects, 10/10 post-closure reuses rejected versus 10/10 permitted by a non-revoking comparator on real repository layouts.
  • The envelope is narrow: mediated tools, sound typed catalog, hand-authored contracts, and subgoals long enough that replay is a real risk. Outside it, short-TTL transport credentials plus narrow task scope close the same gap with less complexity.
  • Closure predicates must evaluate against trusted state only — reading untrusted runtime context to decide whether a handle closes reintroduces the confused-deputy problem this page was meant to close.
  • Pair with task-scope, blast-radius containment, and a closed lethal trifecta leg — the reference monitor is a layer, not a substitute.
Feedback