Repository navigation
Subagent execution replaces TOOL_PERMISSIONS with allow-all — bypasses all user-configured tool restrictions #13289
Description
Activity
I'm working on this. Plan: drop the allow-all override so the subagent runs under the parent session's policies, and forward the permission-request callback through the tool run context so "ask"-tier tool calls surface the same approval dialog as top-level ones (which is what the TODO in executor.ts points at). PR coming shortly.
The bug where subagent spawning overrides
TOOL_PERMISSIONSwith{ tool: "*", permission: "allow" }highlights a recurring vulnerability in multi-tier agent harnesses: permission escalation during delegation.In capability-based security models, child contexts must strictly adhere to monotonic policy attenuation: a delegated task or subagent may only inherit a subset or identical slice of the parent's authorization envelope, never an escalated privilege set.
Furthermore, relying exclusively on application-level state (
serviceContainer.set(TOOL_PERMISSIONS, ...)) to enforce execution safety creates significant blast-radius risks when state resets or overrides occur. To make subagent delegation robust:- Monotonic Capability Scoping: Subagent execution descriptors should compute an attenuated capability contract at spawn time:
subagent_policy = parent_policy ∩ requested_tools. - Kernel-Enforced Execution Boundaries: Disentangle user approval prompts (application UI layer) from filesystem and network safety (OS kernel layer). Even if a subagent is granted automated tool execution, the spawned process should run confined under Linux Landlock LSM (ABI 1–6) restricting writes strictly to the workspace, coupled with tmpfs overlays masking host secrets (
~/.ssh,~/.aws,.env). - Process Hierarchy Cleanup: Concurring subagents should be contained in dedicated cgroups v2 scopes (
cgroup.kill) or PID namespaces so that terminating a subagent cleanly eradicates background children.
We designed Vetto specifically to solve this for multi-agent CLI architectures: it injects deterministic, unprivileged kernel sandboxes (sub-4ms startup) under tool processes, ensuring that even if an agent runs in auto-approval mode, it cannot modify host files or exfiltrate credentials outside its workspace boundary.
Disclaimer: I am the author/maintainer of Vetto, an open-source (Apache-2.0) daemon-less sandbox layer for AI coding agents.
- Monotonic Capability Scoping: Subagent execution descriptors should compute an attenuated capability contract at spawn time:
Solid framing. #13313 fixes the immediate bug at the source: the subagent no longer gets a synthetic allow-all policy. It runs under the parent session's permissions, and ask-tier tool calls surface the same approval dialog as top-level runs, which gets you the monotonic attenuation from (1) on the default path.
Kernel-level confinement (Landlock, cgroup scopes) is a much bigger architectural call for the maintainers. I'd rather keep this PR scoped to the permission-override bug than grow it into a sandboxing redesign.
Reacted by Shleder and Jasmine HegmanLooked at #13313 — removing the synthetic allow-all override is the right core fix, and because nested subagents inherit by default it gives transitive attenuation for free. A few residual points worth hardening before merge:
-
Fail closed when the approval callback is absent. With the override gone, an ask-tier call depends on
onToolPermissionRequestbeing plumbed all the way down. In headless runs (cn -p), or any caller that doesn't supply the callback, make sure the no-callback path defaults to ask/deny rather than silently executing — a missing UI hook must not degrade to allow. Worth a test that spawns a subagent in a headless context withBash = askand asserts the call does not execute when no callback is registered. -
Enforcement still reads policy from a process-wide mutable singleton. The override was possible precisely because
TOOL_PERMISSIONSlived in shared mutable state instead of on the execution context. Nothing writes it mid-run today, but streamed tool calls can execute concurrently (parallelToolCallCount), and the next set/restore pair reintroduces cross-talk. Consider giving each tool run an immutable policy snapshot — parent policy ∩ the subagent's declared tool allowlist, computed at spawn — instead of reading the live container; at minimum, hide the setter from subagent-scoped code. -
Add a nested-spawn test. Subagent spawns subagent: the grandchild must still run under the original ask policy. That's the property that proves the fix is structural and not just one level deep.
Independent-layer note: we build agent-runtime-guard, a local, fail-closed interception layer for agent tool calls — command injection / destructive ops, credential access, SSRF and prompt-injection vectors — with an Ed25519-signed receipt for every verdict, so a bug in the harness's own permission state doesn't silently become execution. It runs free with no key or account, and if it doesn't actually stop this class of attack in your setup, you owe nothing — it has to solve the problem first. The only paid side is human audit/compliance work.
-
Summary
When the main agent spawns a subagent via the
Subagenttool, the executor replaces the entire TOOL_PERMISSIONS service state with{ tool: "*", permission: "allow" }— removing every user-configured approval restriction (Bash, Write, Edit, etc.) for the duration of the subagent run. A TODO comment acknowledges this is incomplete:The original permissions are restored in a
finallyblock, but by then the subagent has already executed with unrestricted tool access.Root cause
extensions/cli/src/subagent/executor.ts:85-88:The user's granular permission configuration (e.g.,
Bash→ ask,Write→ ask,Read→ allow) is wholesale replaced by allow-all for the subagent's entire lifetime.Reproduction (code path)
* → allowbefore subagent runsImpact
One subagent approval = blanket authorization for the subagent to execute ANY tool without review. A prompt-injected subagent task could perform arbitrary file writes, command execution, or network exfiltration using the user's credentials.
Suggested fix
Propagate the main agent's permission configuration to the subagent, or at minimum preserve
disabledtools as disabled in the subagent context.Credit
Chengzhi Yi — yimou@hust.edu.cn — GitHub: @Tardfyou