Skip to content

[RFC] Strict delegation: host-enforced monotonic authority ceilings for subagents #36381

Description

@ayanamislover

Summary

Codex should support a strict, host-enforced least-privilege preflight for every subagent
delegation. Before either V1 or V2 spawn, the parent proposes the filesystem, network, environment,
and tool surface needed for the exact task. The host proves that proposal is a monotonic subset of
the immediate parent's current authority, issues a bounded one-use receipt, and atomically installs
the compiled ceiling before a child thread exists.

This is a harness/runtime property, not a prompt convention or role overlay. In strict mode the spawn
schema requires the receipt, so the parent cannot simply forget the preflight. The host proves
monotonicity; task-semantic minimality remains a planning/product-policy question.

Why the existing primitives are insufficient

A narrower role or prompt can be overwritten or bypassed by live inherited state, direct router or
control calls, runtime approvals, config reload, fork/resume, and host-side capability sources. The
existing request_permissions flow addresses approved expansion during a turn; it does not freeze a
least-privilege child ceiling at delegation time.

Filesystem sandbox policy alone is also incomplete. Hosted tools, MCP/apps/connectors, plugins,
skills, hooks, memory, dynamic tools, code-mode callbacks, and environment/config discovery can have
effects outside a local pathname policy. A strict contract must bind the executable tool/source plan
and the OS sandbox object identity as one capability snapshot.

Product shape: a Strict delegation mode

This could become an opt-in permission mode alongside today's delegation behavior: Strict
delegation mode
. Enabling it would make least-privilege preflight a protocol requirement rather
than an instruction the model may omit. A strict parent would have to prepare and present a valid
receipt for every child; a strict child would inherit a sticky, monotonically narrower ceiling and
the same rule for any grandchild. If the selected platform, sandbox backend, tool runtime, or
capability source cannot enforce the compiled snapshot, the UI/API should report that surface as
unsupported and refuse the spawn before creating a thread.

The reference branch implements this strict delegated execution path behind a default-off feature.
It is not yet a proposal to silently replace existing permission modes or to label every current
sandbox configuration as strict. Productizing it would require an explicit mode name, support
matrix, migration/compatibility decision, and a durable provenance design for resume after process
restart.

Proposed contract

prepare_agent_permissions({
  task_name,
  agent_type?,
  reason,
  environment_id?,
  permission_profile,
  tools
}) -> {
  permission_grant_id?,
  effective_profile?,
  effective_tools,
  is_subset_of_parent,
  diagnostics
}

spawn_agent({
  ...existing_fields,
  task_name,
  permission_grant_id
})

The host should:

  1. resolve and seal the current parent's effective filesystem, network, tool, environment, and
    source authority;
  2. reject lexical non-subsets before any path existence/canonicalization oracle;
  3. compile the requested child policy and prove it is no broader than the immediate parent;
  4. acquire backend-owned filesystem object leases where path-scoped authority requires them;
  5. bind a short-lived, one-use receipt to the parent, exact task, agent kind, local environment,
    canonical policy, tools, source policy, and current parent snapshot;
  6. revalidate and atomically claim the complete snapshot below all public spawn handlers, before
    thread creation; and
  7. repeat the same proof when a child delegates to a grandchild.

Missing, forged, expired, replayed, mismatched, concurrently consumed, or parent-drifted receipts
must fail before a child exists. Ordinary runtime approvals must remain inside the sealed birth
ceiling unless a future explicit replacement-ceiling protocol is designed.

Reference implementation

A default-off research implementation is available on
ayanamislover/agent-permission-preflight
at commit eab87d1f0f035c4bc4f13878a0ab4d90ae8ee16c. The
reviewer packet
contains a design decision, threat model, exact test interpretation, reviewer map, and deliberate
limitations. This is a reference branch, not an implementation PR.

The implementation demonstrates:

  • a mandatory model-visible prepare step for strict V1 and V2 spawn;
  • one shared consume path and an independent lowest-layer atomic claim;
  • immediate-parent monotonicity for nested delegation;
  • a sticky ceiling across permission requests, cached network decisions, shell, apply_patch,
    config mutation, follow-up/interrupt, fork/resume, and cold reload;
  • default-deny delegation support for local, hosted, dynamic, and external tool runtimes;
  • raw parent tool definitions are sealed for consume-time drift detection, while each child receipt
    separately binds the deterministic definition that strict source policy will actually expose;
  • hosted web is withheld for restricted networks and local managed proxy/domain policies unless the
    provider can prove equivalent enforcement, preventing a hosted path from bypassing the local
    network ceiling;
  • source isolation for MCP, apps/connectors, plugins, skills, memory, hooks, shell snapshots, project
    instructions, login shells, role reload, and related pre-tool host entry points;
  • no-child denial assertions below V1/V2 public handlers, event-driven wait over host watch channels,
    and explicit resumable-Completed versus terminal-close lifecycle semantics;
  • bounded receipt state: 8 live grants per parent, 16 per tree, five-minute lifetime, 64 terminal
    tombstones per parent, and 32 object leases per receipt; and
  • fail-closed recovery when a process restart cannot recreate descriptor authority.

Filesystem object identity

On Linux, positive path grants are opened during prepare with openat2 and retained as O_PATH
descriptors. The sandbox helper validates the exact descriptor map and object identity; bubblewrap
uses --bind-fd/--ro-bind-fd instead of reopening a mutable pathname. Descendant grants are derived
relative to the retained parent descriptor with RESOLVE_BENEATH and no-symlink flags.

Real-process adversarial tests cover ordinary replacement, symlink replacement, exact read-only and
deny behavior, .git protection, shell plus apply_patch, and root-child-grandchild derivation. If
neither system nor bundled bubblewrap supports the required FD binds, launch fails closed. Dynamic
deny globs combined with object-bound positive paths are rejected because future matching names
cannot be sealed by a finite descriptor set.

Windows strict children require the elevated backend. Path-scoped grants are rejected until the
Windows sandbox has an equivalent handle-lease contract; the legacy restricted-token backend is
rejected rather than treated as secure.

Verification

Native Windows final workspace gate:

13,010/13,010 passed, 102 skipped, 0 observed retries, 0 failures/errors

One legacy restricted-token delete test is explicitly excluded and separately reproduced on the
unmodified upstream base. It is not counted as branch GREEN, and strict delegation refuses that
backend. A separate zero-retry sweep exposed two unrelated full-load timing flakes; both passed three
isolated zero-retry rounds (6/6), and the clean final full sweep used no retry.

Ubuntu 24.04.3 final workspace gate with real bubblewrap:

13,667/13,667 passed, 23 skipped, 0 retries, 0 failures/errors

The Ubuntu full run used a trap-protected temporary user-namespace window. The AppArmor sysctl was
independently confirmed restored to 1, and /tmp/.git, /tmp/.agents, and /tmp/.codex were
absent afterward. Directed default-AppArmor tests passed 118/118; Linux lifecycle/race stress passed
150/150; the two Windows full-load CLI cases passed 20/20 zero-retry stress before the final full
gate.

Security invariants

  • child_filesystem <= current_parent_filesystem
  • child_network <= current_parent_network
  • child_tools and strict-child definition digests are a supported subset/projection of the
    parent's effective executable tools; raw parent definitions are re-read at consume
  • every nested spawn proves the same relation against its immediate parent
  • denied tools are absent from model-visible schemas and rejected again at dispatch/runtime
  • checked Linux paths stay bound to the same filesystem objects through sandbox launch
  • role/config/source overlays, runtime approvals, fork/resume/reload, and direct control paths cannot
    widen the child
  • unsupported environments, policies, tools, and backends fail before execution
  • model self-report, prompt text, path strings, and diagnostic hashes are not security boundaries

Deliberate limits

  • Local managed environments only; remote executor attestation is out of scope.
  • The host proves monotonicity, not natural-language task minimality.
  • Process-local receipt/descriptor state does not survive restart; durable strict provenance makes
    recovery fail closed.
  • Exact path delegation is unavailable on platforms without an object-handle sandbox contract.
  • External capability sources remain disabled until they expose a receipt-bindable enforcement
    contract. MCP remains unsupported until a provider offers a session-pinned normalized catalog,
    an enforceable revision invalidated by notifications/tools/list_changed, and no silent behavior
    mutation behind an unchanged advertised definition.
  • The research diff is intentionally broad and needs an ownership/landing plan before an
    implementation PR.

Maintainer feedback requested

  1. Does the mandatory prepare -> one-use receipt -> lowest-layer atomic claim shape fit Codex's
    scheduler and protocol ownership model?
  2. Is the Linux descriptor-lease handoff (openat2/O_PATH through bubblewrap FD binds) the right
    backend contract to standardize?
  3. Where should the cross-platform capability object and durable strict provenance live so a partial
    landing cannot expose a bypassable mode?
  4. Should live-child expansion require a replacement receipt/guardian, or should a strict child be
    permanently non-expandable?
  5. Which external tool/source providers, if any, can meet an explicit enforceable and attestable
    delegation contract?

The main request is architectural direction and ownership, not review of a ready-to-merge PR. The
branch leaves unsupported surfaces fail closed and provides the code plus reproducible evidence so
the remaining upstream work can focus on integration choices rather than reconstructing the threat
model.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsandboxIssues related to permissions or sandboxingsubagentIssues involving subagents or multi-agent features

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions