Skip to content

Commit 2149733

Browse files
committed
remove autoapprove workflow
1 parent 59882c2 commit 2149733

32 files changed

Lines changed: 126 additions & 268 deletions

packages/types/src/global-settings.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ export const globalSettingsSchema = z.object({
136136
alwaysAllowModeSwitch: z.boolean().optional(),
137137
alwaysAllowSubtasks: z.boolean().optional(),
138138
alwaysAllowExecute: z.boolean().optional(),
139-
alwaysAllowCommandsExceptDenied: z.boolean().optional(),
140139
destructiveCommandGuardEnabled: z.boolean().optional(),
141140
alwaysAllowFollowupQuestions: z.boolean().optional(),
142141
followupAutoApproveTimeoutMs: z.number().optional(),

packages/types/src/vscode-extension-host.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,6 @@ export type ExtensionState = Pick<
274274
| "alwaysAllowSubtasks"
275275
| "alwaysAllowFollowupQuestions"
276276
| "alwaysAllowExecute"
277-
| "alwaysAllowCommandsExceptDenied"
278277
| "destructiveCommandGuardEnabled"
279278
| "followupAutoApproveTimeoutMs"
280279
| "allowedCommands"

src/core/auto-approval/__tests__/commands.spec.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,6 @@ describe("getCommandDecision", () => {
3636
const result = getCommandDecision(command, ["*"])
3737
expect(result).toBe("auto_approve")
3838
})
39-
40-
describe("allow all except denied mode", () => {
41-
it("auto-approves a command that is not in the allowlist or denylist", () => {
42-
expect(getCommandDecision("unknown command", [], ["rm"], true)).toBe("auto_approve")
43-
})
44-
45-
it("auto-denies a command matching the denylist even if a longer allowlist entry matches", () => {
46-
expect(getCommandDecision("rm --dry-run file", ["rm --dry-run"], ["rm"], true)).toBe("auto_deny")
47-
})
48-
49-
it("auto-denies a command chain when any sub-command matches the denylist", () => {
50-
expect(getCommandDecision("git status && rm file", [], ["rm"], true)).toBe("auto_deny")
51-
})
52-
53-
it("preserves dangerous substitution protection", () => {
54-
expect(getCommandDecision('echo "${var@P}"', [], [], true)).toBe("ask_user")
55-
})
56-
57-
it("preserves malformed command protection", () => {
58-
expect(getCommandDecision("sh -c 'echo a", [], [], true)).toBe("malformed_command")
59-
})
60-
})
6139
})
6240

6341
describe("containsDangerousSubstitution — node -e one-liner false positive regression", () => {

src/core/auto-approval/__tests__/dcg.spec.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ describe("Destructive Command Guard auto-approval precedence", () => {
1515
alwaysAllowFollowupQuestions: false,
1616
allowedCommands: ["echo"],
1717
deniedCommands: ["rm"],
18-
alwaysAllowCommandsExceptDenied: false,
1918
destructiveCommandGuardEnabled: true,
2019
mcpServers: [],
2120
}
2221

23-
it("ignores Zoo's deny list while DCG is enabled", async () => {
22+
it("auto-approves commands allowed by DCG without consulting Zoo's deny list", async () => {
2423
expect(await checkAutoApproval({ state: baseState, ask: "command", text: "rm file" })).toEqual({
25-
decision: "ask",
24+
decision: "approve",
2625
})
2726
})
2827

@@ -32,9 +31,33 @@ describe("Destructive Command Guard auto-approval precedence", () => {
3231
).toEqual({ decision: "ask" })
3332
})
3433

35-
it("retains ordinary allowlist auto-approval for DCG-allowed commands", async () => {
36-
expect(await checkAutoApproval({ state: baseState, ask: "command", text: "echo safe" })).toEqual({
34+
it("auto-approves DCG-allowed commands without consulting Zoo's allowlist", async () => {
35+
expect(await checkAutoApproval({ state: baseState, ask: "command", text: "unlisted-command" })).toEqual({
36+
decision: "approve",
37+
})
38+
})
39+
40+
it("keeps ordinary allowlist auto-approval when DCG is disabled", async () => {
41+
const state = { ...baseState, destructiveCommandGuardEnabled: false }
42+
43+
expect(await checkAutoApproval({ state, ask: "command", text: "echo safe" })).toEqual({
3744
decision: "approve",
3845
})
3946
})
47+
48+
it("keeps ordinary denylist behavior when DCG is disabled", async () => {
49+
const state = { ...baseState, destructiveCommandGuardEnabled: false }
50+
51+
expect(await checkAutoApproval({ state, ask: "command", text: "rm file" })).toEqual({
52+
decision: "deny",
53+
})
54+
})
55+
56+
it("keeps ordinary prompts for unlisted commands when DCG is disabled", async () => {
57+
const state = { ...baseState, destructiveCommandGuardEnabled: false }
58+
59+
expect(await checkAutoApproval({ state, ask: "command", text: "unlisted-command" })).toEqual({
60+
decision: "ask",
61+
})
62+
})
4063
})

src/core/auto-approval/commands.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,7 @@ export type CommandDecision = "auto_approve" | "auto_deny" | "ask_user" | "malfo
217217
* **Decision Logic:**
218218
* 1. **Dangerous Substitution Protection**: Commands with dangerous parameter expansions are never auto-approved
219219
* 2. **Command Parsing**: Split command chains (&&, ||, ;, |, &) into individual commands
220-
* 3. **Individual Validation**: For each sub-command, either apply the longest prefix match rule or, when
221-
* `allowAllExceptDenied` is enabled, approve it unless it matches the denylist
220+
* 3. **Individual Validation**: For each sub-command, apply longest prefix match rule
222221
* 4. **Aggregation**: Combine decisions using "any denial blocks all" principle
223222
*
224223
* **Return Values:**
@@ -253,14 +252,12 @@ export type CommandDecision = "auto_approve" | "auto_deny" | "ask_user" | "malfo
253252
* @param command - The full command string to validate
254253
* @param allowedCommands - List of allowed command prefixes
255254
* @param deniedCommands - Optional list of denied command prefixes
256-
* @param allowAllExceptDenied - Auto-approve commands without a denylist match, ignoring the allowlist
257255
* @returns Decision indicating whether to approve, deny, or ask user
258256
*/
259257
export function getCommandDecision(
260258
command: string,
261259
allowedCommands: string[],
262260
deniedCommands?: string[],
263-
allowAllExceptDenied = false,
264261
): CommandDecision {
265262
if (!command?.trim()) {
266263
return "auto_approve"
@@ -285,10 +282,6 @@ export function getCommandDecision(
285282
// Remove simple PowerShell-like redirections (e.g. 2>&1) before checking
286283
const cmdWithoutRedirection = cmd.replace(/\d*>&\d*/, "").trim()
287284

288-
if (allowAllExceptDenied) {
289-
return findLongestPrefixMatch(cmdWithoutRedirection, deniedCommands || []) ? "auto_deny" : "auto_approve"
290-
}
291-
292285
return getSingleCommandDecision(cmdWithoutRedirection, allowedCommands, deniedCommands)
293286
})
294287

src/core/auto-approval/index.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ export type AutoApprovalStateOptions =
3333
| "mcpServers" // For `alwaysAllowMcp`.
3434
| "allowedCommands" // For `alwaysAllowExecute`.
3535
| "deniedCommands"
36-
| "alwaysAllowCommandsExceptDenied"
3736
| "destructiveCommandGuardEnabled"
3837

3938
export type CheckAutoApprovalResult =
@@ -121,15 +120,15 @@ export async function checkAutoApproval({
121120
return { decision: "ask" }
122121
}
123122

124-
// DCG only changes commands that its policy blocks. Commands that pass
125-
// continue through Zoo's existing allowlist/permission flow.
126123
if (state.alwaysAllowExecute === true) {
127-
const decision = getCommandDecision(
128-
text,
129-
state.allowedCommands || [],
130-
state.destructiveCommandGuardEnabled === true ? [] : state.deniedCommands || [],
131-
state.alwaysAllowCommandsExceptDenied === true,
132-
)
124+
// Execute commands immediately when DCG allows them. ExecuteCommandTool
125+
// marks commands blocked by DCG as protected before reaching this check,
126+
// which keeps the explicit user approval prompt for those commands.
127+
if (state.destructiveCommandGuardEnabled === true) {
128+
return { decision: "approve" }
129+
}
130+
131+
const decision = getCommandDecision(text, state.allowedCommands || [], state.deniedCommands || [])
133132

134133
if (decision === "auto_approve") {
135134
return { decision: "approve" }

src/core/tools/ExecuteCommandTool.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@ export class ExecuteCommandTool extends BaseTool<"execute_command"> {
130130
}
131131
}
132132

133-
// A DCG block is intentionally presented as Zoo's normal command approval
134-
// prompt. Passing isProtected bypasses command auto-approval so the user
135-
// must explicitly choose whether to execute it.
133+
// DCG-approved commands are auto-approved by checkAutoApproval. A DCG
134+
// block is presented as Zoo's normal command prompt, with isProtected
135+
// forcing the user to explicitly choose whether to execute it.
136136
const didApprove = dcgBlocked
137137
? await askApproval("command", canonicalCommand, undefined, true)
138138
: await askApproval("command", canonicalCommand)

src/core/webview/ClineProvider.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2290,7 +2290,6 @@ export class ClineProvider
22902290
alwaysAllowWriteOutsideWorkspace,
22912291
alwaysAllowWriteProtected,
22922292
alwaysAllowExecute,
2293-
alwaysAllowCommandsExceptDenied,
22942293
destructiveCommandGuardEnabled,
22952294
allowedCommands,
22962295
deniedCommands,
@@ -2441,7 +2440,6 @@ export class ClineProvider
24412440
alwaysAllowWriteOutsideWorkspace: alwaysAllowWriteOutsideWorkspace ?? false,
24422441
alwaysAllowWriteProtected: alwaysAllowWriteProtected ?? false,
24432442
alwaysAllowExecute: alwaysAllowExecute ?? false,
2444-
alwaysAllowCommandsExceptDenied: alwaysAllowCommandsExceptDenied ?? false,
24452443
destructiveCommandGuardEnabled: destructiveCommandGuardEnabled ?? false,
24462444
alwaysAllowMcp: alwaysAllowMcp ?? false,
24472445
alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false,
@@ -2675,7 +2673,6 @@ export class ClineProvider
26752673
alwaysAllowWriteOutsideWorkspace: stateValues.alwaysAllowWriteOutsideWorkspace ?? false,
26762674
alwaysAllowWriteProtected: stateValues.alwaysAllowWriteProtected ?? false,
26772675
alwaysAllowExecute: stateValues.alwaysAllowExecute ?? false,
2678-
alwaysAllowCommandsExceptDenied: stateValues.alwaysAllowCommandsExceptDenied ?? false,
26792676
destructiveCommandGuardEnabled: stateValues.destructiveCommandGuardEnabled ?? false,
26802677
alwaysAllowMcp: stateValues.alwaysAllowMcp ?? false,
26812678
alwaysAllowModeSwitch: stateValues.alwaysAllowModeSwitch ?? false,

src/core/webview/__tests__/ClineProvider.spec.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -949,22 +949,21 @@ describe("ClineProvider", () => {
949949
expect(state).toHaveProperty("writeDelayMs")
950950
})
951951

952-
test("getStateToPostToWebview returns the saved allow-all-except-denied setting", async () => {
952+
test("getStateToPostToWebview returns the saved destructive command guard setting", async () => {
953953
await provider.resolveWebviewView(mockWebviewView)
954-
await provider.contextProxy.setValue("alwaysAllowCommandsExceptDenied", true)
954+
await provider.contextProxy.setValue("destructiveCommandGuardEnabled", true)
955955

956956
const state = await provider.getStateToPostToWebview()
957957

958-
expect(state.alwaysAllowCommandsExceptDenied).toBe(true)
958+
expect(state.destructiveCommandGuardEnabled).toBe(true)
959959
})
960960

961-
test("getStateToPostToWebview returns the saved destructive command guard setting", async () => {
961+
test("getStateToPostToWebview disables destructive command guard by default", async () => {
962962
await provider.resolveWebviewView(mockWebviewView)
963-
await provider.contextProxy.setValue("destructiveCommandGuardEnabled", true)
964963

965964
const state = await provider.getStateToPostToWebview()
966965

967-
expect(state.destructiveCommandGuardEnabled).toBe(true)
966+
expect(state.destructiveCommandGuardEnabled).toBe(false)
968967
})
969968

970969
test("language is set to VSCode language", async () => {

webview-ui/src/components/chat/CommandExecution.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ interface CommandExecutionProps {
4242
export const CommandExecution = ({ executionId, text, icon, title, isDenied = false }: CommandExecutionProps) => {
4343
const {
4444
terminalShellIntegrationDisabled = false,
45-
alwaysAllowCommandsExceptDenied = false,
4645
allowedCommands = [],
4746
deniedCommands = [],
4847
setAllowedCommands,
@@ -247,7 +246,7 @@ export const CommandExecution = ({ executionId, text, icon, title, isDenied = fa
247246
<CodeBlock source={command} language="shell" />
248247
<OutputContainer isExpanded={isExpanded} output={output} />
249248
</div>
250-
{command && command.trim() && !alwaysAllowCommandsExceptDenied && !isDenied && (
249+
{command && command.trim() && !isDenied && (
251250
<CommandPatternSelector
252251
patterns={commandPatterns}
253252
allowedCommands={allowedCommands}

0 commit comments

Comments
 (0)