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/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"); });