fix: preserve sudo shell carrier commands

This commit is contained in:
Peter Steinberger
2026-05-03 15:46:21 +01:00
parent 809f5ae150
commit 7d26fb32a7
5 changed files with 63 additions and 16 deletions

View File

@@ -908,7 +908,7 @@ describe("exec approval handlers", () => {
twoPhase: true,
host: "gateway",
command: "python3 -c 'print(1)'",
commandArgv: ["python3", "-c", "print(1)"],
commandArgv: ["python3", "script.py"],
systemRunPlan: undefined,
nodeId: undefined,
},

View File

@@ -31,11 +31,11 @@ describe("command-analysis explanation summary", () => {
expect(summary.warningLines).toEqual(["Contains inline-eval: python3 -c"]);
});
it("resolves display summaries from argv or shell commands", () => {
it("resolves node display summaries from argv", () => {
expect(
resolveCommandAnalysisSummaryForDisplay({
host: "gateway",
commandText: "echo ok",
host: "node",
commandText: "python3 script.py",
commandArgv: ["python3", "-c", "print(1)"],
}),
).toEqual(
@@ -51,6 +51,29 @@ describe("command-analysis explanation summary", () => {
commandText: "python3 -c 'print(1)'",
}),
).toBeNull();
});
it("resolves gateway display summaries from shell text even when argv is stale", () => {
expect(
resolveCommandAnalysisSummaryForDisplay({
host: "gateway",
commandText: "python3 -c 'print(1)'",
commandArgv: ["python3", "script.py"],
}),
).toEqual(
expect.objectContaining({
commandCount: 1,
riskKinds: ["inline-eval"],
warningLines: ["Contains inline-eval: python3 -c"],
}),
);
expect(
resolveCommandAnalysisSummaryForDisplay({
host: "gateway",
commandText: "echo ok",
commandArgv: ["python3", "-c", "print(1)"],
})?.riskKinds,
).toEqual([]);
expect(
resolveCommandAnalysisSummaryForDisplay({
host: "gateway",

View File

@@ -89,19 +89,19 @@ export function resolveCommandAnalysisSummaryForDisplay(params: {
sanitizeText?: (value: string) => string;
}): CommandExplanationSummary | null {
const analysis =
Array.isArray(params.commandArgv) && params.commandArgv.length > 0
? analyzeCommandForPolicy({
source: "argv",
argv: params.commandArgv,
cwd: params.cwd ?? undefined,
})
: params.host === "node"
? null
: analyzeCommandForPolicy({
source: "shell",
command: params.commandText,
params.host === "node"
? Array.isArray(params.commandArgv) && params.commandArgv.length > 0
? analyzeCommandForPolicy({
source: "argv",
argv: params.commandArgv,
cwd: params.cwd ?? undefined,
});
})
: null
: analyzeCommandForPolicy({
source: "shell",
command: params.commandText,
cwd: params.cwd ?? undefined,
});
if (!analysis?.ok) {
return null;
}

View File

@@ -18,6 +18,9 @@ describe("command-analysis risks", () => {
);
expect(detectInlineEvalArgv(["sudo", "-uroot", "python3", "-c", "print(1)"])?.flag).toBe("-c");
expect(detectInlineEvalArgv(["sudo", "-EH", "python3", "-c", "print(1)"])?.flag).toBe("-c");
expect(detectInlineEvalArgv(["sudo", "-i", "python3", "-c", "print(1)"])?.flag).toBe("-c");
expect(detectInlineEvalArgv(["sudo", "-s", "python3", "-c", "print(1)"])?.flag).toBe("-c");
expect(detectInlineEvalArgv(["sudo", "--shell", "python3", "-c", "print(1)"])?.flag).toBe("-c");
expect(detectInlineEvalArgv(["sudo", "-Eu", "root", "python3", "-c", "print(1)"])?.flag).toBe(
"-c",
);
@@ -106,6 +109,18 @@ describe("command-analysis risks", () => {
expect(buildCommandPayloadCandidates(["sudo", "-EH", "/approve", "abc"])).toEqual([
"/approve abc",
]);
expect(buildCommandPayloadCandidates(["sudo", "-i", "/approve", "abc"])).toEqual([
"/approve abc",
]);
expect(buildCommandPayloadCandidates(["sudo", "-s", "/approve", "abc"])).toEqual([
"/approve abc",
]);
expect(buildCommandPayloadCandidates(["sudo", "--shell", "/approve", "abc"])).toEqual([
"/approve abc",
]);
expect(buildCommandPayloadCandidates(["sudo", "--preserve-groups", "/approve", "abc"])).toEqual(
["/approve abc"],
);
expect(
buildCommandPayloadCandidates(["sudo", "-uroot", "bash", "-lc", "/approve req allow-once"]),
).toEqual(["bash -lc /approve req allow-once", "/approve req allow-once"]);

View File

@@ -46,18 +46,27 @@ const SUDO_OPTIONS_WITH_VALUE = new Set([
]);
const SUDO_STANDALONE_OPTIONS = new Set([
"-A",
"-B",
"-b",
"-E",
"-H",
"-i",
"-N",
"-n",
"-P",
"-S",
"-s",
"--askpass",
"--background",
"--bell",
"--login",
"--no-update",
"--non-interactive",
"--preserve-env",
"--preserve-groups",
"--reset-home",
"--set-home",
"--shell",
"--stdin",
]);
const SUDO_NON_EXEC_OPTIONS = new Set([