fix(security): detect obfuscated commands that bypass allowlist filters (#24287)

* security(exec): add obfuscated command detector

* test(exec): cover obfuscation detector patterns

* security(exec): enforce obfuscation approval on gateway host

* security(exec): enforce obfuscation approval on node host

* test(exec): prevent obfuscation timeout bypass

* chore(changelog): credit obfuscation security fix
This commit is contained in:
Vincent Koc
2026-02-23 02:50:06 -05:00
committed by GitHub
parent 7568ae52ce
commit 0e28e50b45
6 changed files with 422 additions and 9 deletions

View File

@@ -14,7 +14,9 @@ import {
resolveAllowAlwaysPatterns,
resolveExecApprovals,
} from "../infra/exec-approvals.js";
import { detectCommandObfuscation } from "../infra/exec-obfuscation-detect.js";
import type { SafeBinProfile } from "../infra/exec-safe-bin-policy.js";
import { logInfo } from "../logger.js";
import { markBackgrounded, tail } from "./bash-process-registry.js";
import { requestExecApprovalDecisionForHost } from "./bash-tools.exec-approval-request.js";
import {
@@ -81,6 +83,11 @@ export async function processGatewayAllowlist(
const analysisOk = allowlistEval.analysisOk;
const allowlistSatisfied =
hostSecurity === "allowlist" && analysisOk ? allowlistEval.allowlistSatisfied : false;
const obfuscation = detectCommandObfuscation(params.command);
if (obfuscation.detected) {
logInfo(`exec: obfuscation detected (gateway): ${obfuscation.reasons.join(", ")}`);
params.warnings.push(`⚠️ Obfuscated command detected: ${obfuscation.reasons.join("; ")}`);
}
const recordMatchedAllowlistUse = (resolvedPath?: string) => {
if (allowlistMatches.length === 0) {
return;
@@ -105,7 +112,9 @@ export async function processGatewayAllowlist(
security: hostSecurity,
analysisOk,
allowlistSatisfied,
}) || requiresHeredocApproval;
}) ||
requiresHeredocApproval ||
obfuscation.detected;
if (requiresHeredocApproval) {
params.warnings.push(
"Warning: heredoc execution requires explicit approval in allowlist mode.",
@@ -154,7 +163,9 @@ export async function processGatewayAllowlist(
if (decision === "deny") {
deniedReason = "user-denied";
} else if (!decision) {
if (askFallback === "full") {
if (obfuscation.detected) {
deniedReason = "approval-timeout (obfuscation-detected)";
} else if (askFallback === "full") {
approvedByAsk = true;
} else if (askFallback === "allowlist") {
if (!analysisOk || !allowlistSatisfied) {