Skip to content

Subagent execution replaces TOOL_PERMISSIONS with allow-all — bypasses all user-configured tool restrictions #13289

Description

@Tardfyou

Summary

When the main agent spawns a subagent via the Subagent tool, 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:

// allow all tools for now
// todo: eventually we want to show the same prompt in a dialog whether asking
// whether that tool call is allowed or not

The original permissions are restored in a finally block, but by then the subagent has already executed with unrestricted tool access.

Root cause

extensions/cli/src/subagent/executor.ts:85-88:

serviceContainer.set<ToolPermissionServiceState>(
    SERVICE_NAMES.TOOL_PERMISSIONS,
    { permissions: { policies: [{ tool: "*", permission: "allow" }] } },
);

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)

  1. User configures granular tool permissions (e.g., Bash → ask, Write → ask)
  2. Main agent spawns a subagent for a focused task
  3. Executor sets TOOL_PERMISSIONS to * → allow before subagent runs
  4. Subagent can execute ANY tool (Bash, Write, network, etc.) without prompting
  5. Original permissions restored only after subagent completes

Impact

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 disabled tools as disabled in the subagent context.

Credit

Chengzhi Yi — yimou@hust.edu.cn — GitHub: @Tardfyou

Activity

  1. lakshya-dhariwal commented on Sep 24, 2026

    @lakshya-dhariwal

    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.

  2. shleder commented on Sep 25, 2026

    @shleder

    The bug where subagent spawning overrides TOOL_PERMISSIONS with { 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:

    1. Monotonic Capability Scoping: Subagent execution descriptors should compute an attenuated capability contract at spawn time: subagent_policy = parent_policy ∩ requested_tools.
    2. 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).
    3. 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.

  3. lakshya-dhariwal commented on Sep 26, 2026

    @lakshya-dhariwal

    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.

  4. DSHCorrectover commented on Oct 5, 2026

    @DSHCorrectover

    Looked 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:

    1. Fail closed when the approval callback is absent. With the override gone, an ask-tier call depends on onToolPermissionRequest being 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 with Bash = ask and asserts the call does not execute when no callback is registered.

    2. Enforcement still reads policy from a process-wide mutable singleton. The override was possible precisely because TOOL_PERMISSIONS lived 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.

    3. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions