fix(security): block shell-wrapper line-continuation allowlist bypass

This commit is contained in:
Peter Steinberger
2026-02-22 22:36:29 +01:00
parent 7c109f5737
commit 3f0b9dbb36
6 changed files with 132 additions and 37 deletions

View File

@@ -166,6 +166,37 @@ export async function handleSystemRunInvoke(opts: {
const cmdInvocation = shellCommand
? opts.isCmdExeInvocation(segments[0]?.argv ?? [])
: opts.isCmdExeInvocation(argv);
const policy = evaluateSystemRunPolicy({
security,
ask,
analysisOk,
allowlistSatisfied,
approvalDecision,
approved: opts.params.approved === true,
isWindows,
cmdInvocation,
shellWrapperInvocation: shellCommand !== null,
});
analysisOk = policy.analysisOk;
allowlistSatisfied = policy.allowlistSatisfied;
if (!policy.allowed) {
await opts.sendNodeEvent(
opts.client,
"exec.denied",
opts.buildExecEventPayload({
sessionKey,
runId,
host: "node",
command: cmdText,
reason: policy.eventReason,
}),
);
await opts.sendInvokeResult({
ok: false,
error: { code: "UNAVAILABLE", message: policy.errorMessage },
});
return;
}
const useMacAppExec = opts.preferMacAppExecHost;
if (useMacAppExec) {
@@ -232,37 +263,6 @@ export async function handleSystemRunInvoke(opts: {
}
}
const policy = evaluateSystemRunPolicy({
security,
ask,
analysisOk,
allowlistSatisfied,
approvalDecision,
approved: opts.params.approved === true,
isWindows,
cmdInvocation,
});
analysisOk = policy.analysisOk;
allowlistSatisfied = policy.allowlistSatisfied;
if (!policy.allowed) {
await opts.sendNodeEvent(
opts.client,
"exec.denied",
opts.buildExecEventPayload({
sessionKey,
runId,
host: "node",
command: cmdText,
reason: policy.eventReason,
}),
);
await opts.sendInvokeResult({
ok: false,
error: { code: "UNAVAILABLE", message: policy.errorMessage },
});
return;
}
if (policy.approvalDecision === "allow-always" && security === "allowlist") {
if (policy.analysisOk) {
const patterns = resolveAllowAlwaysPatterns({