feat(exec-approval): default to full/off security and support no-timeout

- Change default security from "allowlist" to "full" (allow all commands)
- Change default ask from "on-miss" to "off" (never prompt)
- Change DEFAULT_APPROVAL_TIMEOUT_MS from 60s to -1 (no timeout)
- Support timeoutMs=-1 to wait indefinitely for user decision
- Update CLI and Hub approval flows to skip setTimeout when timeout<0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yushen 2026-02-06 18:02:07 +08:00
parent 91aa433f34
commit a36cbac3fd
4 changed files with 38 additions and 34 deletions

View file

@ -50,8 +50,8 @@ export function createCliApprovalCallback(
const runtimeConfig = { ...config, allowlist: [...(config.allowlist ?? [])] };
return async (command: string, cwd: string | undefined): Promise<ApprovalResult> => {
const security = runtimeConfig.security ?? "allowlist";
const ask = runtimeConfig.ask ?? "on-miss";
const security = runtimeConfig.security ?? "full";
const ask = runtimeConfig.ask ?? "off";
const timeoutMs = runtimeConfig.timeoutMs ?? DEFAULT_APPROVAL_TIMEOUT_MS;
// Security: deny blocks everything
@ -137,13 +137,15 @@ function promptTerminal(
rl.close();
};
// Timeout: auto-deny
const timer = setTimeout(() => {
if (resolved) return;
process.stderr.write(dim(`\n Approval timed out (${timeoutMs / 1000}s). Denying.\n\n`));
cleanup();
resolve("deny");
}, timeoutMs);
// Timeout: auto-deny (skip if timeoutMs is -1 for no timeout)
const timer = timeoutMs >= 0
? setTimeout(() => {
if (resolved) return;
process.stderr.write(dim(`\n Approval timed out (${timeoutMs / 1000}s). Denying.\n\n`));
cleanup();
resolve("deny");
}, timeoutMs)
: null;
// Display approval prompt
process.stderr.write("\n");
@ -161,7 +163,7 @@ function promptTerminal(
rl.question(
` ${bold("[a]")}llow once / ${bold("[A]")}llow always / ${bold("[d]")}eny (default: deny): `,
(answer) => {
clearTimeout(timer);
if (timer) clearTimeout(timer);
cleanup();
const trimmed = answer.trim();
@ -177,7 +179,7 @@ function promptTerminal(
// Handle Ctrl+C gracefully
rl.on("close", () => {
clearTimeout(timer);
if (timer) clearTimeout(timer);
if (!resolved) {
resolved = true;
resolve("deny");

View file

@ -50,7 +50,7 @@ export interface ExecApprovalConfig {
security?: ExecSecurity;
/** Ask mode: "off" never asks, "on-miss" asks when allowlist misses, "always" always asks */
ask?: ExecAsk;
/** Timeout before auto-deny in milliseconds (default: 60_000) */
/** Timeout before auto-deny in milliseconds (default: 60_000). Set to -1 for no timeout. */
timeoutMs?: number;
/** Fallback security level on timeout (default: "deny" — fail-closed) */
askFallback?: ExecSecurity;
@ -58,8 +58,8 @@ export interface ExecApprovalConfig {
allowlist?: ExecAllowlistEntry[];
}
/** Default timeout for approval requests (60 seconds) */
export const DEFAULT_APPROVAL_TIMEOUT_MS = 60_000;
/** Default timeout for approval requests (-1 = no timeout, wait indefinitely) */
export const DEFAULT_APPROVAL_TIMEOUT_MS = -1;
// ============ Allowlist ============