fix(security): bind system.run approvals to exact argv text

This commit is contained in:
Peter Steinberger
2026-03-11 01:25:19 +00:00
parent 8eac939417
commit 7289c19f1a
17 changed files with 241 additions and 34 deletions

View File

@@ -22,6 +22,7 @@ type HardeningCase = {
expectedArgvChanged?: boolean;
expectedCmdText?: string;
checkRawCommandMatchesArgv?: boolean;
expectedCommandPreview?: string | null;
};
type ScriptOperandFixture = {
@@ -101,6 +102,7 @@ describe("hardenApprovedExecutionPaths", () => {
argv: ["env", "sh", "-c", "echo SAFE"],
expectedArgv: () => ["env", "sh", "-c", "echo SAFE"],
expectedCmdText: "echo SAFE",
expectedCommandPreview: "echo SAFE",
},
{
name: "preserves dispatch-wrapper argv during approval hardening",
@@ -135,6 +137,16 @@ describe("hardenApprovedExecutionPaths", () => {
withPathToken: true,
expectedArgv: ({ pathToken }) => [pathToken!.expected, "hello"],
checkRawCommandMatchesArgv: true,
expectedCommandPreview: null,
},
{
name: "stores full approval text and preview for path-qualified env wrappers",
mode: "build-plan",
argv: ["./env", "sh", "-c", "echo SAFE"],
expectedArgv: () => ["./env", "sh", "-c", "echo SAFE"],
expectedCmdText: "echo SAFE",
checkRawCommandMatchesArgv: true,
expectedCommandPreview: "echo SAFE",
},
];
@@ -168,6 +180,9 @@ describe("hardenApprovedExecutionPaths", () => {
if (testCase.checkRawCommandMatchesArgv) {
expect(prepared.plan.rawCommand).toBe(formatExecCommand(prepared.plan.argv));
}
if ("expectedCommandPreview" in testCase) {
expect(prepared.plan.commandPreview ?? null).toBe(testCase.expectedCommandPreview);
}
return;
}

View File

@@ -650,9 +650,11 @@ export function buildSystemRunApprovalPlan(params: {
if (!hardening.ok) {
return { ok: false, message: hardening.message };
}
const rawCommand = hardening.argvChanged
? formatExecCommand(hardening.argv) || null
: command.cmdText.trim() || null;
const rawCommand = formatExecCommand(hardening.argv) || null;
const commandPreview =
command.previewText?.trim() && command.previewText.trim() !== rawCommand
? command.previewText.trim()
: null;
const mutableFileOperand = resolveMutableFileOperandSnapshotSync({
argv: hardening.argv,
cwd: hardening.cwd,
@@ -666,10 +668,11 @@ export function buildSystemRunApprovalPlan(params: {
argv: hardening.argv,
cwd: hardening.cwd ?? null,
rawCommand,
commandPreview,
agentId: normalizeString(params.agentId),
sessionKey: normalizeString(params.sessionKey),
mutableFileOperand: mutableFileOperand.snapshot ?? undefined,
},
cmdText: rawCommand ?? formatExecCommand(hardening.argv),
cmdText: commandPreview ?? rawCommand ?? formatExecCommand(hardening.argv),
};
}