fix(core): replace hardcoded non-interactive ASK_USER denial with explicit policy rules (#23668)

This commit is contained in:
ruomeng
2026-03-26 14:35:12 -04:00
committed by GitHub
parent aa4d9316a9
commit c888da5f73
13 changed files with 207 additions and 66 deletions
+14 -20
View File
@@ -244,8 +244,10 @@ export class PolicyEngine {
}
}
this.defaultDecision = config.defaultDecision ?? PolicyDecision.ASK_USER;
this.nonInteractive = config.nonInteractive ?? false;
this.defaultDecision =
config.defaultDecision ??
(this.nonInteractive ? PolicyDecision.DENY : PolicyDecision.ASK_USER);
this.disableAlwaysAllow = config.disableAlwaysAllow ?? false;
this.checkerRunner = checkerRunner;
this.approvalMode = config.approvalMode ?? ApprovalMode.DEFAULT;
@@ -340,7 +342,7 @@ export class PolicyEngine {
): Promise<CheckResult> {
if (!command) {
return {
decision: this.applyNonInteractiveMode(ruleDecision),
decision: ruleDecision,
rule,
};
}
@@ -363,13 +365,13 @@ export class PolicyEngine {
}
debugLogger.debug(
`[PolicyEngine.check] Command parsing failed for: ${command}. Falling back to ASK_USER.`,
`[PolicyEngine.check] Command parsing failed for: ${command}. Falling back to ${this.defaultDecision}.`,
);
// Parsing logic failed, we can't trust it. Force ASK_USER (or DENY).
// Parsing logic failed, we can't trust it. Use default decision ASK_USER (or DENY in non-interactive).
// We return the rule that matched so the evaluation loop terminates.
return {
decision: this.applyNonInteractiveMode(PolicyDecision.ASK_USER),
decision: this.defaultDecision,
rule,
};
}
@@ -466,7 +468,7 @@ export class PolicyEngine {
}
return {
decision: this.applyNonInteractiveMode(aggregateDecision),
decision: aggregateDecision,
// If we stayed at ALLOW, we return the original rule (if any).
// If we downgraded, we return the responsible rule (or undefined if implicit).
rule: aggregateDecision === ruleDecision ? rule : responsibleRule,
@@ -474,7 +476,7 @@ export class PolicyEngine {
}
return {
decision: this.applyNonInteractiveMode(ruleDecision),
decision: ruleDecision,
rule,
};
}
@@ -597,7 +599,7 @@ export class PolicyEngine {
break;
}
} else {
decision = this.applyNonInteractiveMode(rule.decision);
decision = rule.decision;
matchedRule = rule;
break;
}
@@ -641,7 +643,7 @@ export class PolicyEngine {
decision = shellResult.decision;
matchedRule = shellResult.rule;
} else {
decision = this.applyNonInteractiveMode(this.defaultDecision);
decision = this.defaultDecision;
}
}
@@ -697,7 +699,7 @@ export class PolicyEngine {
}
return {
decision: this.applyNonInteractiveMode(decision),
decision,
rule: matchedRule,
};
}
@@ -866,7 +868,7 @@ export class PolicyEngine {
continue;
} else {
// Unconditional rule for this tool
const decision = this.applyNonInteractiveMode(rule.decision);
const decision = rule.decision;
staticallyExcluded = decision === PolicyDecision.DENY;
matchFound = true;
break;
@@ -876,7 +878,7 @@ export class PolicyEngine {
if (!matchFound) {
// Fallback to default decision if no rule matches
const defaultDec = this.applyNonInteractiveMode(this.defaultDecision);
const defaultDec = this.defaultDecision;
if (defaultDec === PolicyDecision.DENY) {
staticallyExcluded = true;
}
@@ -889,12 +891,4 @@ export class PolicyEngine {
return excludedTools;
}
private applyNonInteractiveMode(decision: PolicyDecision): PolicyDecision {
// In non-interactive mode, ASK_USER becomes DENY
if (this.nonInteractive && decision === PolicyDecision.ASK_USER) {
return PolicyDecision.DENY;
}
return decision;
}
}