fix(write): evaluate function-valued xd:// device approvals

The write approval gate discarded a mounted tool's function-valued
approval and never decoded the device JSON payload, defaulting the tier
to exec. Read/write xd:// operations then prompted in non-yolo modes
that permit them.

Now decode valid object payloads and resolve the mounted tool's normal
approval decision via resolveToolTier; malformed JSON, non-object
payloads, and unknown devices still fall back to exec and prompt.

Fixes #5727
This commit is contained in:
roboomp
2026-07-16 17:24:06 +00:00
parent c0d0ad7629
commit 5445beb6f5
4 changed files with 76 additions and 7 deletions
+17 -4
View File
@@ -36,7 +36,7 @@ import {
writeArchive,
} from "../utils/zip";
import { routeWriteThroughBridge } from "./acp-bridge";
import { truncateForPrompt } from "./approval";
import { resolveToolTier, truncateForPrompt } from "./approval";
import { assertEditableFile } from "./auto-generated-guard";
import {
type ConflictEntry,
@@ -398,9 +398,22 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
if (xdevTarget.name === REPORT_ISSUE_DEVICE_NAME) return "write";
if (xdevTarget.name && isResolutionDeviceName(xdevTarget.name)) return "read";
const inst = xdevTarget.name ? this.session.xdevRegistry?.get(xdevTarget.name) : undefined;
const decision = typeof inst?.approval === "function" ? undefined : inst?.approval;
const tier = typeof decision === "object" ? decision?.tier : decision;
return tier ?? "exec";
if (!inst) return "exec";
// Decode the device JSON payload and evaluate the mounted tool's own
// approval (which may be argument-dependent, e.g. ast_edit is read-tier
// for internal-URL paths, debug is read-tier for inspection actions).
// Malformed JSON, non-object payloads, and missing content stay exec so
// the gate fails closed — the dispatch itself rejects them too.
const rawContent = (args as Partial<WriteParams>).content;
if (typeof rawContent !== "string") return "exec";
let parsed: unknown;
try {
parsed = JSON.parse(rawContent);
} catch {
return "exec";
}
if (!isRecord(parsed)) return "exec";
return resolveToolTier(inst, parsed);
}
// Remote SSH writes open an outbound connection and run a remote shell —
// gate them like the exec-tier `ssh` tool, ahead of the handler-write