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

@@ -16,6 +16,7 @@ export type SystemRunCommandValidation =
ok: true;
shellCommand: string | null;
cmdText: string;
previewText: string | null;
}
| {
ok: false;
@@ -30,6 +31,7 @@ export type ResolvedSystemRunCommand =
rawCommand: string | null;
shellCommand: string | null;
cmdText: string;
previewText: string | null;
}
| {
ok: false;
@@ -112,35 +114,35 @@ export function validateSystemRunCommandConsistency(params: {
const envManipulationBeforeShellWrapper =
shellWrapperResolution.isWrapper && hasEnvManipulationBeforeShellWrapper(params.argv);
const mustBindDisplayToFullArgv = envManipulationBeforeShellWrapper || shellWrapperPositionalArgv;
const inferred =
shellCommand !== null && !mustBindDisplayToFullArgv
? shellCommand.trim()
: formatExecCommand(params.argv);
const formattedArgv = formatExecCommand(params.argv);
const legacyShellText =
shellCommand !== null && !mustBindDisplayToFullArgv ? shellCommand.trim() : null;
const previewText = legacyShellText;
const cmdText = raw ?? legacyShellText ?? formattedArgv;
if (raw && raw !== inferred) {
return {
ok: false,
message: "INVALID_REQUEST: rawCommand does not match command",
details: {
code: "RAW_COMMAND_MISMATCH",
rawCommand: raw,
inferred,
},
};
if (raw) {
const matchesCanonicalArgv = raw === formattedArgv;
const matchesLegacyShellText = legacyShellText !== null && raw === legacyShellText;
if (!matchesCanonicalArgv && !matchesLegacyShellText) {
return {
ok: false,
message: "INVALID_REQUEST: rawCommand does not match command",
details: {
code: "RAW_COMMAND_MISMATCH",
rawCommand: raw,
inferred: legacyShellText ?? formattedArgv,
formattedArgv,
},
};
}
}
return {
ok: true,
// Only treat this as a shell command when argv is a recognized shell wrapper.
// For direct argv execution and shell wrappers with env prelude modifiers,
// rawCommand is purely display/approval text and must match the formatted argv.
shellCommand:
shellCommand !== null
? envManipulationBeforeShellWrapper
? shellCommand
: (raw ?? shellCommand)
: null,
cmdText: raw ?? inferred,
shellCommand: shellCommand !== null ? shellCommand : null,
cmdText,
previewText,
};
}
@@ -167,6 +169,7 @@ export function resolveSystemRunCommand(params: {
rawCommand: null,
shellCommand: null,
cmdText: "",
previewText: null,
};
}
@@ -189,5 +192,6 @@ export function resolveSystemRunCommand(params: {
rawCommand: raw,
shellCommand: validation.shellCommand,
cmdText: validation.cmdText,
previewText: validation.previewText,
};
}