Exec approvals: reject shell init-file script matches (#58369)

This commit is contained in:
Jacob Tomlinson 2026-03-31 06:53:43 -07:00 committed by GitHub
parent 0ed4f8a72b
commit 0c83754246
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 63 additions and 7 deletions

View File

@ -120,6 +120,30 @@ describe("resolveAllowAlwaysPatterns", () => {
expect(second.allowlistSatisfied).toBe(true);
}
function expectShellScriptFallbackRejected(command: string) {
const { dir, scriptsDir, script, env, safeBins } = createShellScriptFixture();
const rcFile = path.join(scriptsDir, "evilrc");
fs.writeFileSync(rcFile, "echo blocked\n");
const { persisted } = resolvePersistedPatterns({
command,
dir,
env,
safeBins,
});
expect(persisted).toEqual([]);
const second = evaluateShellAllowlist({
command,
allowlist: [{ pattern: script }],
safeBins,
cwd: dir,
env,
platform: process.platform,
});
expect(second.allowlistSatisfied).toBe(false);
}
function expectPositionalArgvCarrierRejected(command: string) {
const dir = makeTempDir();
const touch = makeExecutable(dir, "touch");
@ -283,6 +307,32 @@ describe("resolveAllowAlwaysPatterns", () => {
});
});
it("rejects shell rc and init-file options as persisted or allowlisted script paths", () => {
if (process.platform === "win32") {
return;
}
for (const command of [
"bash --rcfile scripts/evilrc scripts/save_crystal.sh",
"bash --init-file scripts/evilrc scripts/save_crystal.sh",
"bash --startup-file scripts/evilrc scripts/save_crystal.sh",
]) {
expectShellScriptFallbackRejected(command);
}
});
it("rejects shell rc and init-file equals options as persisted or allowlisted script paths", () => {
if (process.platform === "win32") {
return;
}
for (const command of [
"bash --rcfile=scripts/evilrc scripts/save_crystal.sh",
"bash --init-file=scripts/evilrc scripts/save_crystal.sh",
"bash --startup-file=scripts/evilrc scripts/save_crystal.sh",
]) {
expectShellScriptFallbackRejected(command);
}
});
it("rejects shell-wrapper positional argv carriers", () => {
if (process.platform === "win32") {
return;

View File

@ -504,16 +504,19 @@ function isShellWrapperSegment(segment: ExecCommandSegment): boolean {
return hasSegmentExecutableMatch(segment, isShellWrapperExecutable);
}
const SHELL_WRAPPER_OPTIONS_WITH_VALUE = new Set([
"-c",
"--command",
"-o",
"-O",
"+O",
const SHELL_WRAPPER_OPTIONS_WITH_VALUE = new Set(["-c", "--command", "-o", "-O", "+O"]);
const SHELL_WRAPPER_DISQUALIFYING_SCRIPT_OPTIONS = [
"--rcfile",
"--init-file",
"--startup-file",
]);
] as const;
function hasDisqualifyingShellWrapperScriptOption(token: string): boolean {
return SHELL_WRAPPER_DISQUALIFYING_SCRIPT_OPTIONS.some(
(option) => token === option || token.startsWith(`${option}=`),
);
}
function resolveShellWrapperScriptCandidatePath(params: {
segment: ExecCommandSegment;
@ -548,6 +551,9 @@ function resolveShellWrapperScriptCandidatePath(params: {
if (token === "-s" || /^-[^-]*s[^-]*$/i.test(token)) {
return undefined;
}
if (hasDisqualifyingShellWrapperScriptOption(token)) {
return undefined;
}
if (SHELL_WRAPPER_OPTIONS_WITH_VALUE.has(token)) {
idx += 2;
continue;