Files
ThothII/backend/scripts/revision-state-policy.test.mjs

274 lines
14 KiB
JavaScript

import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { validatePythonRevisionStates, validateRevisionState } from "./revision-state-policy.mjs";
function rejects(source, path) {
assert.throws(() => validateRevisionState(source, path), /revision-state/, source);
}
function passes(source, path) {
assert.doesNotThrow(() => validateRevisionState(source, path));
}
test("PowerShell scoped and braced revision variables remain executable", () => {
rejects('${revision}.state', "scripts/direct.ps1");
rejects('${workspaceRevision}["state"]', "scripts/bracket.ps1");
rejects('Write-Output "$(${selectedWorkspace}.state)"', "scripts/subexpression.ps1");
rejects('${script:revision}.state', "scripts/scoped.ps1");
rejects('${global:workspaceRevision}["state"]', "scripts/global.ps1");
for (const source of [
'$REVISION.STATE',
'${Revision}.state',
'$WORKSPACEREVISION["STATE"]',
'${GLOBAL:SELECTEDWORKSPACE}.State',
'$REVISION["ST" + "ATE"]',
'${Revision}[("sT" + "AtE")]',
'$record.REVISION.STATE',
'$record.WORKSPACEREVISION["STATE"]',
'$record["REVISION"].STATE',
]) rejects(source, "scripts/case.ps1");
passes('REVISION.STATE; revision.STATE; revision["ST" + "ATE"]; record.REVISION.STATE; record["REVISION"].state', "backend/src/case-sensitive.ts");
});
test("Bash jq command forms and associative revision parameters are active", () => {
for (const source of [
"value=$(jq -r '.revision.state' snapshot.json)",
"value=$(command jq -r '.workspaceRevision.state' snapshot.json)",
"/usr/bin/jq --arg x y '.selectedWorkspace.state' snapshot.json",
"env -i MODE=x jq -- '.revision.state' snapshot.json",
"env -u MODE /opt/tools/jq -r '.workspaceRevision.state' snapshot.json",
"echo safe\nvalue=`jq -r '.selectedWorkspace.state' snapshot.json`",
"sudo -u nobody /usr/bin/jq -r '.revision.state' snapshot.json",
"nice -n 5 jq -r '.workspaceRevision.state' snapshot.json",
"time jq -r '.selectedWorkspace.state' snapshot.json",
"printf x | xargs -n 1 jq -r '.revision.state'",
"timeout -k 2 5 jq -r '.revision.state' snapshot.json",
`stdbuf -o L jq -r '.["workspaceRevision"].state' snapshot.json`,
`stdbuf -oL jq -r '.["selectedWorkspace"]["state"]' snapshot.json`,
`nohup jq -r '.revision["state"]' snapshot.json`,
"< snapshot.json jq -r '.workspaceRevision.state'",
"sudo MODE=x jq -r '.selectedWorkspace.state' snapshot.json",
String.raw`jq -r '"x \(.revision.state)"' snapshot.json`,
"jq < snapshot.json -r '.revision.state'",
"jq -r < snapshot.json '.workspaceRevision.state'",
"jq --arg note safe < snapshot.json '.selectedWorkspace.state'",
"jq -r '.revision?.state' snapshot.json",
`jq -r '.["workspaceRevision"]?["state"]' snapshot.json`,
`jq -r '.["revision"]?.["state"]' snapshot.json`,
`jq -r '."revision".state' snapshot.json`,
`jq -r '."workspaceRevision"."state"' snapshot.json`,
"jq -r '$revision.state' snapshot.json",
"jq -r '($selectedWorkspace).state' snapshot.json",
`${"env ".repeat(17)}jq -r '.revision.state' snapshot.json`,
"jq<input.json -r '.revision.state'",
"jq 2>/dev/null -r '.workspaceRevision.state' snapshot.json",
"{ jq -r '.selectedWorkspace.state' snapshot.json; }",
"! jq -r '.revision.state' snapshot.json",
"if jq -r '.workspaceRevision.state' snapshot.json; then :; fi",
"if false; then :; elif jq -r '.selectedWorkspace.state' snapshot.json; then :; fi",
"while false; do jq -r '.revision.state' snapshot.json; done",
"until false; do jq -r '.workspaceRevision.state' snapshot.json; done",
"jq -r '.revision | .state' snapshot.json",
"jq -r '(.workspaceRevision | .state)' snapshot.json",
`jq -r '.["revi" + "sion"].state' snapshot.json`,
`jq -r '.["workspace" + "Revision"]["st" + "ate"]' snapshot.json`,
"jq 2>&1 -r '.revision.state' snapshot.json",
"jq 2>&- -r '.workspaceRevision.state' snapshot.json",
"jq 0<&3 -r '.selectedWorkspace.state' snapshot.json",
"jq &>/dev/null -r '.revision.state' snapshot.json",
"jq &>>log -r '.workspaceRevision.state' snapshot.json",
"jq >|output -r '.selectedWorkspace.state' snapshot.json",
"jq {fd}>output -r '.revision.state' snapshot.json",
"exec jq -r '.workspaceRevision.state' snapshot.json",
"coproc jq -r '.selectedWorkspace.state' snapshot.json",
"coproc worker jq -r '.revision.state' snapshot.json",
"coproc worker >out jq -r '.workspaceRevision.state' snapshot.json",
"coproc worker 2>/dev/null jq -r '.selectedWorkspace.state' snapshot.json",
"coproc worker VAR=x jq -r '.revision.state' snapshot.json",
`jq -r '.["revi" + ("sion")].state' snapshot.json`,
"jq -r '.revision | . | .state' snapshot.json",
"jq -r '(.workspaceRevision | (.) | .state)' snapshot.json",
"jq -r '.revision | select(.) | .state' snapshot.json",
"jq -r '.workspaceRevision | {value:.state}' snapshot.json",
"jq -r '.selectedWorkspace | [.state]' snapshot.json",
"jq -r '.revision + .state' snapshot.json",
"jq < <(cat snapshot.json) -r '.revision.state'",
"jq < <(cat <(printf snapshot.json)) -r '.workspaceRevision.state'",
"jq > >(cat >/dev/null) -r '.selectedWorkspace.state' snapshot.json",
`jq < <(printf '%s\n' "$((1 + (2)))") -r '.revision.state'`,
"jq < <(cat snapshot.json -r '.revision.state'",
`${"<(".repeat(65)}echo snapshot${")".repeat(65)} jq -r '.workspaceRevision.state'`,
"cat <(jq -r '.revision.state' snapshot.json)",
"cat snapshot.json > >(jq -r '.workspaceRevision.state')",
`echo "$(jq -r '.selectedWorkspace.state' snapshot.json)"`,
"value=$(jq -r '.revision.state' snapshot.json)",
`echo "\`jq -r '.workspaceRevision.state' snapshot.json\`"`,
`echo "$(cat <(jq -r '.selectedWorkspace.state' snapshot.json))"`,
`${"$(".repeat(33)}jq -r '.revision.state' snapshot.json${")".repeat(33)}`,
`echo "$(( $(jq -r '.revision.state' snapshot.json) + 0 ))"`,
"echo \"$(( `jq -r '.workspaceRevision.state' snapshot.json` + 0 ))\"",
"echo `echo \\`jq -r '.selectedWorkspace.state' snapshot.json\\``",
"echo \"`echo \\`jq -r '.revision.state' snapshot.json\\``\"",
`${"$(( ".repeat(33)}$(jq -r '.workspaceRevision.state' snapshot.json)${" + 0 ))".repeat(33)}`,
'old=${revision["state"]}',
"old=${workspaceRevision[state]}",
"old=${revision[state]:-missing}",
"old=${workspaceRevision['state']:=missing}",
"old=${selectedWorkspace[state]:1:2}",
]) rejects(source, "scripts/policy.sh");
const jqFilters = [
".revision?.state", '.["revision"]?.["state"]', '."revision".state',
'."workspaceRevision"."state"', "(.revision).state", "$revision.state",
".revision | .state", "(.workspaceRevision | .state)",
'.["revi" + "sion"].state', '.["revi" + ("sion")].state',
".revision | . | .state", "(.workspaceRevision | (.) | .state)",
".revision | select(.) | .state", ".workspaceRevision | {value:.state}",
'.revision | ["state"]', '(.workspaceRevision | (["state"]))', ".selectedWorkspace | $state",
];
for (const filter of jqFilters) {
const compiled = spawnSync("jq", ["-n", "--argjson", "revision", "{}", "--arg", "state", "x", filter], { encoding: "utf8" });
if (compiled.error?.code !== "ENOENT") assert.equal(compiled.status, 0, `${filter}: ${compiled.stderr}`);
}
passes("cat <<'EOF'\nrevision.state\nEOF\n", "scripts/literal.sh");
passes("echo '${revision[state]}'\n", "scripts/single-quoted-parameter.sh");
passes(`echo "<(jq '.revision.state')"\n`, "scripts/literal-process-text.sh");
passes(`echo "ordinary jq '.workspaceRevision.state' text"\n`, "scripts/literal-jq-text.sh");
passes(`echo '$(jq -r ".selectedWorkspace.state")'\n`, "scripts/single-quoted-command-text.sh");
passes(`# profile's harmless note
printf 'ok\n'
`, "scripts/comment-apostrophe.sh");
passes(`cat <( # profile's harmless note
printf 'snapshot\n'
)
`, "scripts/substitution-comment-apostrophe.sh");
passes(`echo "$(( 1 + (2 * 3) ))"\n`, "scripts/literal-arithmetic.sh");
passes(`echo $(( jq + revision + state ))\n`, "scripts/arithmetic-identifiers.sh");
passes("echo \\`jq -r '.revision.state' snapshot.json\\`\n", "scripts/escaped-literal-backticks.sh");
passes("echo \"\\`jq -r '.workspaceRevision.state' snapshot.json\\`\"\n", "scripts/double-quoted-literal-backticks.sh");
passes("echo `printf '%s' '\\`jq -r \".selectedWorkspace.state\" snapshot.json\\`'`\n", "scripts/quoted-nonexecuting-nested-backticks.sh");
passes("jq --arg note 'revision.state' '.' file\n", "scripts/jq-arg.sh");
passes(`jq --argjson note '"revision.state"' '.' file
`, "scripts/jq-argjson.sh");
passes("jq -r '.' revision.state.json\n", "scripts/jq-file.sh");
passes("jq -r '.revision.id' snapshot.json\n", "scripts/jq-simple-non-state.sh");
passes("jq -f revision.state.jq snapshot.json\n", "scripts/jq-from-file.sh");
passes("jq --from-file workspaceRevision.state.jq snapshot.json\n", "scripts/jq-long-from-file.sh");
passes(`jq -r '"revision.state"' snapshot.json
`, "scripts/jq-string.sh");
passes(`jq -r '{note:"selectedWorkspace.state"}' snapshot.json
`, "scripts/jq-object.sh");
passes(`jq -r '.revision | "state"' snapshot.json
`, "scripts/jq-pipe-literal-right.sh");
passes(`jq -r '"revision" | .state' snapshot.json
`, "scripts/jq-pipe-literal-left.sh");
passes(`jq -r '.revision | ["state"]' snapshot.json
`, "scripts/jq-pipe-array.sh");
passes(`jq -r '(.workspaceRevision | (["state"]))' snapshot.json
`, "scripts/jq-pipe-parenthesized-array.sh");
passes(`jq --arg state x '.selectedWorkspace | $state' snapshot.json
`, "scripts/jq-pipe-variable.sh");
for (const opener of ["'E'OF", "E'OF'", "E\\OF"]) {
passes(`cat <<${opener}
revision.state
EOF
`, "scripts/partial-quoted-heredoc.sh");
}
rejects("cat <<'E'OF\nrevision.state\nEOF\nworkspaceRevision.state\n", "scripts/after-heredoc.sh");
rejects("cat <<'EOF'\nrevision.state\n", "scripts/unclosed-heredoc.sh");
rejects(`echo "<<'EOF'"
jq -r '.revision.state' snapshot.json
`, "scripts/quoted-opener.sh");
});
test("Python helper resolves active AST expressions and static format bindings", () => {
const rejectsPython = (source) => assert.throws(
() => validatePythonRevisionStates([{ source, label: "backend/scripts/policy.py" }]),
/revision-state/,
);
for (const source of [
"old = revision.state",
'old = workspaceRevision["state"]',
'old = record["selectedWorkspace"].state',
'old = f"{revision.state}"',
'"{revision.state}".format(value)',
'"{0.state}".format(revision)',
'"{0[state]}".format(workspaceRevision)',
'"{item.state}".format(item=selectedWorkspace)',
'"{item[state]}".format_map({"item": revision})',
'("{0.state}").format(revision)',
'"{0:{1.state}}".format(value, revision)',
'old = revision["st" + "ate"]',
'old = record["revi" + "sion"].state',
'old = revision[f"state"]',
'old = record[f"revision"].state',
`old = revision[f"st{'a'}te"]`,
`old = revision[f"{'state'}"]`,
`old = record[f"revi{'sion'}"].state`,
`old = revision[f"{'st' + 'ate'}"]`,
`old = record[f"{'revi' + 'sion'}"].state`,
`old = revision[f"{'state':s}"]`,
'"{0.state}".format(*[revision])',
'"{0[state]}".format(*(revision,))',
'"{1[state]}".format(*[other, workspaceRevision])',
'"{item.state}".format(**{"item": selectedWorkspace})',
'"{item[state]}".format_map({**{"item": revision}})',
'"{.state}".format(revision)',
'"{[state]}".format(revision)',
'"{:{.state}}".format(value, revision)',
'"{.name} {[state]}".format(other, revision)',
'"{item.state}".format(item=revision, **values)',
]) rejectsPython(source);
validatePythonRevisionStates([
{ source: 'text = "{revision.state}"', label: "backend/scripts/literal.py" },
{ source: 'text = "{{revision.state}}".format(value)', label: "backend/scripts/escaped.py" },
{ source: 'text = "{0.state}".format(other)', label: "backend/scripts/unrelated.py" },
{ source: 'old = revision[f"st{suffix}"]', label: "backend/scripts/dynamic-key.py" },
{ source: 'text = "{.name} {[state]}".format(other, other)', label: "backend/scripts/multi-auto.py" },
{ source: 'text = "{item.state}".format(**values)', label: "backend/scripts/dynamic-map.py" },
]);
const hostile = mkdtempSync(join(tmpdir(), "revision-policy-hostile-"));
writeFileSync(join(hostile, "json.py"), "raise RuntimeError('shadowed')\n");
const previousPythonPath = process.env.PYTHONPATH;
try {
process.env.PYTHONPATH = hostile;
validatePythonRevisionStates([{ source: "value = 1", label: "backend/scripts/isolated.py" }]);
} finally {
if (previousPythonPath === undefined) delete process.env.PYTHONPATH;
else process.env.PYTHONPATH = previousPythonPath;
rmSync(hostile, { recursive: true, force: true });
}
assert.throws(
() => validatePythonRevisionStates([{ source: 'revision[f"{1:.1000000000f}"]', label: "backend/scripts/oversized.py" }]),
/revision-state helper failed/,
);
validatePythonRevisionStates([{ source: 'revision[f"{1:04d}"]', label: "backend/scripts/small-format.py" }]);
assert.throws(
() => validatePythonRevisionStates([{ source: "def broken(", label: "backend/scripts/invalid.py" }]),
/revision-state helper failed/,
);
});
test("YAML mappings and only active plain scalar expressions are rejected", () => {
for (const source of [
"value: { revision: { state: old } }\n",
"value:\n workspaceRevision:\n state: old\n",
'items:\n - "selectedWorkspace":\n "state": old\n',
"old: selectedWorkspace.state\n",
"url: https://host/x; old: selectedWorkspace.state\n",
"saved: &saved { state: old }\nvalue: { revision: *saved }\n",
"defaults: &defaults { workspaceRevision: { state: old } }\nvalue: { <<: *defaults }\n",
]) rejects(source, "scripts/policy.yaml");
rejects("a: &a [x,x,x,x,x,x,x,x,x]\nb: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]\nc: [*b,*b,*b,*b,*b,*b,*b,*b,*b]\n", "scripts/alias-bomb.yaml");
rejects("value: [\n", "scripts/invalid.yaml");
for (const source of [
"value: |\n revision.state\n",
"value: >\n workspaceRevision.state\n",
'value: "selectedWorkspace.state"\n',
"url: https://host/revision.state\n",
]) passes(source, "scripts/literal.yaml");
});