refactor sensitive-file-guard tests to use node:test

This commit is contained in:
Claudio Ortolina
2026-05-11 22:26:38 +01:00
parent cc0bfd4791
commit 9d0c8acb04
@@ -1,217 +1,312 @@
/**
* Tests for extractCommandNames from ast.ts using Node.js built-in node:test runner.
*
* Run with: node --experimental-strip-types --test extractCommandNames.test.ts
*/
import { describe, it } from "node:test";
import * as assert from "node:assert/strict";
import { extractCommandNames } from "./ast.ts"; import { extractCommandNames } from "./ast.ts";
// --- Test runner --- // ── Test helpers ────────────────────────────────────────────────────────────
const blocked = new Set(["printenv", "env"]); const blocked = new Set(["printenv", "env"]);
const tests: { function checkBlocked(
cmd: string; cmd: string,
expected: "BLOCKED" | "ALLOWED"; expected: "BLOCKED" | "ALLOWED",
reason: string; _reason: string,
}[] = [ ): void {
// Simple commands
{ cmd: "printenv", expected: "BLOCKED", reason: "Simple command match" },
{ cmd: "env FOO=bar", expected: "BLOCKED", reason: "Simple command match" },
{
cmd: "FOO=bar printenv",
expected: "BLOCKED",
reason: "Assignment prefix — printenv is still the command",
},
{
cmd: "FOO=bar env",
expected: "BLOCKED",
reason: "Assignment prefix — env is still the command",
},
// Command substitution — blocked commands inside $() or backticks run
{ cmd: "$(env)", expected: "BLOCKED", reason: "Command substitution" },
{ cmd: "$(printenv)", expected: "BLOCKED", reason: "Command substitution" },
{
cmd: "`env`",
expected: "BLOCKED",
reason: "Backtick command substitution",
},
{
cmd: "echo $(env)",
expected: "BLOCKED",
reason: "env inside command substitution in echo argument",
},
{
cmd: "FOO=$(env) echo hi",
expected: "BLOCKED",
reason: "env inside command substitution in assignment value",
},
// Subshell
{ cmd: "(env)", expected: "BLOCKED", reason: "Subshell — env runs inside" },
// Pipeline — any member triggers a block
{
cmd: "env | grep FOO",
expected: "BLOCKED",
reason: "Pipeline — env is first command",
},
{
cmd: "grep FOO | env",
expected: "BLOCKED",
reason: "Pipeline — env is second command",
},
{
cmd: "printenv | env",
expected: "BLOCKED",
reason: "Both blocked commands in pipeline",
},
// Compound lists and control flow
{
cmd: "for f in *; do env; done",
expected: "BLOCKED",
reason: "env in for loop body",
},
{
cmd: "for f in $(env); do echo $f; done",
expected: "BLOCKED",
reason: "env in for wordlist command substitution",
},
{
cmd: "for ((i=0;i<10;i++)); do env; done",
expected: "BLOCKED",
reason: "env in arithmetic for",
},
{
cmd: "while true; do env; done",
expected: "BLOCKED",
reason: "env in while body",
},
{
cmd: "until false; do env; done",
expected: "BLOCKED",
reason: "env in until body (parsed as While with kind:until)",
},
{
cmd: "if true; then env; fi",
expected: "BLOCKED",
reason: "env in if-then",
},
{
cmd: "case x in a) env;; esac",
expected: "BLOCKED",
reason: "env in case body",
},
{
cmd: "case $(env) in a) echo hi;; esac",
expected: "BLOCKED",
reason: "env in Case.word substitution",
},
{ cmd: "true && env", expected: "BLOCKED", reason: "env in AndOr (&&)" },
{
cmd: "select f in $(env); do echo $f; done",
expected: "BLOCKED",
reason: "env in select wordlist",
},
// Function definition, brace group, coproc
{
cmd: "foo() { env; }",
expected: "BLOCKED",
reason: "env in function body",
},
{ cmd: "{ env; }", expected: "BLOCKED", reason: "env in brace group" },
{ cmd: "coproc env", expected: "BLOCKED", reason: "env in coproc body" },
// --- ALLOWED: blocked command name is NOT at an executable position ---
{
cmd: "echo printenv",
expected: "ALLOWED",
reason: "printenv is an argument to echo, not a command",
},
{ cmd: "# printenv", expected: "ALLOWED", reason: "Comment — not executed" },
{
cmd: 'echo "use env to..."',
expected: "ALLOWED",
reason: "Inside double-quoted string",
},
{
cmd: "echo 'printenv'",
expected: "ALLOWED",
reason: "Inside single-quoted string",
},
{
cmd: "(echo printenv)",
expected: "ALLOWED",
reason: "printenv is argument to echo inside subshell",
},
{
cmd: "$(echo env)",
expected: "ALLOWED",
reason: "env is argument to echo inside command substitution",
},
{
cmd: 'echo "use env" | grep FOO',
expected: "ALLOWED",
reason: "env in double-quoted string in pipeline",
},
// --- ALLOWED: command is not in the blocked set ---
{
cmd: "export FOO=bar",
expected: "ALLOWED",
reason: "export is not in blocked_commands",
},
{
cmd: "ls -la",
expected: "ALLOWED",
reason: "ls is not in blocked_commands",
},
{
cmd: "echo hi; echo there",
expected: "ALLOWED",
reason: "Semicolon-separated harmless commands",
},
{
cmd: "echo hi || echo fail",
expected: "ALLOWED",
reason: "Or-separated harmless commands",
},
{
cmd: "[[ -f file ]]",
expected: "ALLOWED",
reason: "TestCommand — no commands executed",
},
// --- ALLOWED: no command at all ---
{ cmd: "", expected: "ALLOWED", reason: "Empty command" },
{
cmd: "FOO=bar",
expected: "ALLOWED",
reason: "Bare assignment, no command name",
},
{
cmd: ">/dev/null",
expected: "ALLOWED",
reason: "Redirection only, no command name",
},
];
let passed = 0;
let failed = 0;
for (const { cmd, expected, reason } of tests) {
const names = extractCommandNames(cmd); const names = extractCommandNames(cmd);
const hasBlocked = [...names].some((n) => blocked.has(n)); const hasBlocked = [...names].some((n) => blocked.has(n));
const actual = hasBlocked ? "BLOCKED" : "ALLOWED"; const actual = hasBlocked ? "BLOCKED" : "ALLOWED";
assert.equal(
if (actual === expected) { actual,
passed++; expected,
console.log(`${expected.padEnd(7)} | ${cmd.padEnd(45)} | ${reason}`); `cmd: "${cmd}" — names found: [${[...names].join(", ")}]`,
} else { );
failed++;
console.log(
`❌ expected ${expected.padEnd(7)} got ${actual.padEnd(7)} | ${cmd.padEnd(45)} | ${reason}`,
);
console.log(` Command names found: [${[...names].join(", ")}]`);
}
} }
console.log(`\n${passed} passed, ${failed} failed`); // ── BLOCKED: Simple commands ────────────────────────────────────────────────
if (failed > 0) process.exit(1);
describe("extractCommandNames — simple commands", () => {
it("blocks a simple blocked command", () => {
checkBlocked("printenv", "BLOCKED", "Simple command match");
});
it("blocks env with prefix assignment", () => {
checkBlocked("env FOO=bar", "BLOCKED", "Simple command match");
});
it("blocks printenv with assignment prefix", () => {
checkBlocked(
"FOO=bar printenv",
"BLOCKED",
"Assignment prefix — printenv is still the command",
);
});
it("blocks env with assignment prefix", () => {
checkBlocked(
"FOO=bar env",
"BLOCKED",
"Assignment prefix — env is still the command",
);
});
});
// ── BLOCKED: Command substitution ──────────────────────────────────────────
describe("extractCommandNames — command substitution", () => {
it("blocks $(env)", () => {
checkBlocked("$(env)", "BLOCKED", "Command substitution");
});
it("blocks $(printenv)", () => {
checkBlocked("$(printenv)", "BLOCKED", "Command substitution");
});
it("blocks backtick env", () => {
checkBlocked("`env`", "BLOCKED", "Backtick command substitution");
});
it("blocks env inside command substitution in echo argument", () => {
checkBlocked(
"echo $(env)",
"BLOCKED",
"env inside command substitution in echo argument",
);
});
it("blocks env inside command substitution in assignment value", () => {
checkBlocked(
"FOO=$(env) echo hi",
"BLOCKED",
"env inside command substitution in assignment value",
);
});
});
// ── BLOCKED: Subshell ──────────────────────────────────────────────────────
describe("extractCommandNames — subshell", () => {
it("blocks env in subshell", () => {
checkBlocked("(env)", "BLOCKED", "Subshell — env runs inside");
});
});
// ── BLOCKED: Pipeline ──────────────────────────────────────────────────────
describe("extractCommandNames — pipeline", () => {
it("blocks env as first command in pipeline", () => {
checkBlocked(
"env | grep FOO",
"BLOCKED",
"Pipeline — env is first command",
);
});
it("blocks env as second command in pipeline", () => {
checkBlocked(
"grep FOO | env",
"BLOCKED",
"Pipeline — env is second command",
);
});
it("blocks both blocked commands in pipeline", () => {
checkBlocked(
"printenv | env",
"BLOCKED",
"Both blocked commands in pipeline",
);
});
});
// ── BLOCKED: Compound lists and control flow ───────────────────────────────
describe("extractCommandNames — compound lists and control flow", () => {
it("blocks env in for loop body", () => {
checkBlocked("for f in *; do env; done", "BLOCKED", "env in for loop body");
});
it("blocks env in for wordlist command substitution", () => {
checkBlocked(
"for f in $(env); do echo $f; done",
"BLOCKED",
"env in for wordlist command substitution",
);
});
it("blocks env in arithmetic for", () => {
checkBlocked(
"for ((i=0;i<10;i++)); do env; done",
"BLOCKED",
"env in arithmetic for",
);
});
it("blocks env in while body", () => {
checkBlocked("while true; do env; done", "BLOCKED", "env in while body");
});
it("blocks env in until body (parsed as While with kind:until)", () => {
checkBlocked(
"until false; do env; done",
"BLOCKED",
"env in until body (parsed as While with kind:until)",
);
});
it("blocks env in if-then", () => {
checkBlocked("if true; then env; fi", "BLOCKED", "env in if-then");
});
it("blocks env in case body", () => {
checkBlocked("case x in a) env;; esac", "BLOCKED", "env in case body");
});
it("blocks env in Case.word substitution", () => {
checkBlocked(
"case $(env) in a) echo hi;; esac",
"BLOCKED",
"env in Case.word substitution",
);
});
it("blocks env in AndOr (&&)", () => {
checkBlocked("true && env", "BLOCKED", "env in AndOr (&&)");
});
it("blocks env in select wordlist", () => {
checkBlocked(
"select f in $(env); do echo $f; done",
"BLOCKED",
"env in select wordlist",
);
});
});
// ── BLOCKED: Function definition, brace group, coproc ──────────────────────
describe("extractCommandNames — function, brace, coproc", () => {
it("blocks env in function body", () => {
checkBlocked("foo() { env; }", "BLOCKED", "env in function body");
});
it("blocks env in brace group", () => {
checkBlocked("{ env; }", "BLOCKED", "env in brace group");
});
it("blocks env in coproc body", () => {
checkBlocked("coproc env", "BLOCKED", "env in coproc body");
});
});
// ── ALLOWED: Blocked name at non-executable position ───────────────────────
describe("extractCommandNames — blocked name not at executable position", () => {
it("allows printenv as argument to echo", () => {
checkBlocked(
"echo printenv",
"ALLOWED",
"printenv is an argument to echo, not a command",
);
});
it("allows printenv in comment", () => {
checkBlocked("# printenv", "ALLOWED", "Comment — not executed");
});
it("allows env in double-quoted string", () => {
checkBlocked(
'echo "use env to..."',
"ALLOWED",
"Inside double-quoted string",
);
});
it("allows printenv in single-quoted string", () => {
checkBlocked("echo 'printenv'", "ALLOWED", "Inside single-quoted string");
});
it("allows printenv as argument to echo inside subshell", () => {
checkBlocked(
"(echo printenv)",
"ALLOWED",
"printenv is argument to echo inside subshell",
);
});
it("allows env as argument to echo inside command substitution", () => {
checkBlocked(
"$(echo env)",
"ALLOWED",
"env is argument to echo inside command substitution",
);
});
it("allows env in double-quoted string in pipeline", () => {
checkBlocked(
'echo "use env" | grep FOO',
"ALLOWED",
"env in double-quoted string in pipeline",
);
});
});
// ── ALLOWED: Command not in blocked set ────────────────────────────────────
describe("extractCommandNames — command not in blocked set", () => {
it("allows export", () => {
checkBlocked(
"export FOO=bar",
"ALLOWED",
"export is not in blocked_commands",
);
});
it("allows ls", () => {
checkBlocked("ls -la", "ALLOWED", "ls is not in blocked_commands");
});
it("allows semicolon-separated harmless commands", () => {
checkBlocked(
"echo hi; echo there",
"ALLOWED",
"Semicolon-separated harmless commands",
);
});
it("allows OR-separated harmless commands", () => {
checkBlocked(
"echo hi || echo fail",
"ALLOWED",
"Or-separated harmless commands",
);
});
it("allows TestCommand ([[ ... ]])", () => {
checkBlocked(
"[[ -f file ]]",
"ALLOWED",
"TestCommand — no commands executed",
);
});
});
// ── ALLOWED: No command at all ─────────────────────────────────────────────
describe("extractCommandNames — no command at all", () => {
it("allows empty command", () => {
checkBlocked("", "ALLOWED", "Empty command");
});
it("allows bare assignment with no command name", () => {
checkBlocked("FOO=bar", "ALLOWED", "Bare assignment, no command name");
});
it("allows redirection only with no command name", () => {
checkBlocked(">/dev/null", "ALLOWED", "Redirection only, no command name");
});
});