fix(task): subagent inherits parent agent's deny rules (Plan Mode security bypass) (#26597)

Co-authored-by: Developer <temp@example.com>
This commit is contained in:
Kit Langton
2026-05-09 19:51:55 -04:00
committed by GitHub
parent 6849f96825
commit b8ca71d309
3 changed files with 185 additions and 26 deletions

View File

@@ -4,6 +4,7 @@ import { Session } from "@/session/session"
import { SessionID, MessageID } from "../session/schema"
import { MessageV2 } from "../session/message-v2"
import { Agent } from "../agent/agent"
import { deriveSubagentSessionPermission } from "../agent/subagent-permissions"
import type { SessionPrompt } from "../session/prompt"
import { Config } from "@/config/config"
import { Effect, Exit, Schema } from "effect"
@@ -58,41 +59,25 @@ export const TaskTool = Tool.define(
return yield* Effect.fail(new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`))
}
const canTask = next.permission.some((rule) => rule.permission === id)
const canTodo = next.permission.some((rule) => rule.permission === "todowrite")
const taskID = params.task_id
const session = taskID
? yield* sessions.get(SessionID.make(taskID)).pipe(Effect.catchCause(() => Effect.succeed(undefined)))
: undefined
const parent = yield* sessions.get(ctx.sessionID)
const parentAgent = parent.agent
? yield* agent.get(parent.agent).pipe(Effect.catchCause(() => Effect.succeed(undefined)))
: undefined
const nextSession =
session ??
(yield* sessions.create({
parentID: ctx.sessionID,
title: params.description + ` (@${next.name} subagent)`,
permission: [
...(parent.permission ?? []).filter(
(rule) => rule.permission === "external_directory" || rule.action === "deny",
),
...(canTodo
? []
: [
{
permission: "todowrite" as const,
pattern: "*" as const,
action: "deny" as const,
},
]),
...(canTask
? []
: [
{
permission: id,
pattern: "*" as const,
action: "deny" as const,
},
]),
...deriveSubagentSessionPermission({
parentSessionPermission: parent.permission ?? [],
parentAgent,
subagent: next,
}),
...(cfg.experimental?.primary_tools?.map((item) => ({
pattern: "*",
action: "allow" as const,
@@ -144,8 +129,8 @@ export const TaskTool = Tool.define(
},
agent: next.name,
tools: {
...(canTodo ? {} : { todowrite: false }),
...(canTask ? {} : { task: false }),
...(next.permission.some((rule) => rule.permission === "todowrite") ? {} : { todowrite: false }),
...(next.permission.some((rule) => rule.permission === id) ? {} : { task: false }),
...Object.fromEntries((cfg.experimental?.primary_tools ?? []).map((item) => [item, false])),
},
parts,