Skip to content

apply_diff approval payload omits isOutsideWorkspace — diffs outside the workspace are auto-approved, ignoring alwaysAllowWriteOutsideWorkspace and allowedWriteFiles #2249

Closed

Description

@tvanomr

Summary

apply_diff builds its appliedDiff approval payload without the isOutsideWorkspace field. The auto-approval decision treats a missing value as "inside the workspace", so with alwaysAllowWrite: true, every apply_diff is auto-approved for any path on disk — even when alwaysAllowWriteOutsideWorkspace: false and the target matches no allowedWriteFiles glob. write_to_file and edit prompt correctly in the same situation; apply_diff silently applies.

Environment

  • Zoo Code extension 3.92.0 (marketplace bundle); the same code is present on main (src/core/tools/ApplyDiffTool.ts @ 4011f802)
  • VSCodium, Linux
  • Settings: autoApprovalEnabled: true, alwaysAllowWrite: true, alwaysAllowWriteOutsideWorkspace: false, alwaysAllowWriteProtected: false, allowedWriteFiles not matching the target path

Steps to reproduce

  1. Configure auto-approval as above.
  2. Ask the agent to apply_diff a file outside the workspace (absolute path or a ../-relative path), e.g. ~/.config/some-app/config.json.
  3. The diff is applied without an approval prompt.
  4. Contrast: write_to_file targeting the same directory prompts as expected.

Root cause (source on main)

src/core/tools/ApplyDiffTool.ts — both approval messages spread sharedMessageProps, which never carries the flag:

const sharedMessageProps: ClineSayTool = {
	tool: "appliedDiff",
	path: getReadablePath(task.cwd, relPath),
	diff: diffContent,
}

handlePartial's props have the same omission (cosmetic only — partial asks don't gate the write — but inconsistent with the other tools).

The decision in src/core/auto-approval/index.ts (checkAutoApproval) defaults a missing field to false:

const isOutsideWorkspace = !!tool.isOutsideWorkspace

if (isWriteToolAction(tool)) {
	return state.alwaysAllowWrite === true &&
		(!isOutsideWorkspace || state.alwaysAllowWriteOutsideWorkspace === true) &&
		(!isProtected || state.alwaysAllowWriteProtected === true)
		? { decision: "approve" }
		: { decision: "ask" }
}

So apply_diff is classified as inside-workspace no matter where the file is, and alwaysAllowWriteOutsideWorkspace / allowedWriteFiles are effectively bypassed for this tool.

The sibling tools do it correctly — src/core/tools/EditTool.ts:

const isOutsideWorkspace = isPathOutsideWorkspace(absolutePath)

const sharedMessageProps: ClineSayTool = {
	tool: "appliedDiff",
	path: getReadablePath(task.cwd, relPath),
	diff: sanitizedDiff,
	isOutsideWorkspace,
}

ApplyDiffTool.ts already computes absolutePath = path.resolve(task.cwd, relPath) — it just never consults isPathOutsideWorkspace.

Expected

apply_diff should honor alwaysAllowWriteOutsideWorkspace and allowedWriteFiles exactly like edit / write_to_file: outside-workspace diffs must prompt unless the path matches the write allowlist.

Suggested fix

import { isPathOutsideWorkspace } from "../../utils/pathUtils"
// ...
const isOutsideWorkspace = isPathOutsideWorkspace(absolutePath)

const sharedMessageProps: ClineSayTool = {
	tool: "appliedDiff",
	path: getReadablePath(task.cwd, relPath),
	diff: diffContent,
	isOutsideWorkspace,
}

(plus the same field in handlePartial's props for consistency). Happy to follow up with a PR.

Observed impact

With the settings above, an agent task edited files under ~/.config/ with no approval while the user believed outside-workspace writes were gated. Verified by side-by-side probes in the same session: write_to_file → prompt; apply_diff → silent apply (reproduced both with absolute and ../-traversal relative paths). A local bundle patch adding the field restores correct prompting and allowedWriteFiles allowlisting for apply_diff.

Activity

  1. kanchinadd98 commented on Nov 7, 2026

    @kanchinadd98
    Contributor

    Claiming

  2. added a commit that references this issue on Nov 9, 2026
    1b9cb7d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

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