shoc-pr-review-runner/tests/test-gate-integrity.sh

85 lines
3.2 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# Regression test for the gate-forgery attack confirmed by the pre-push
# security review.
#
# Threat: the build gates execute code authored in the PR under review. That
# code runs as the same user and can write the gate table, either overriding a
# genuine FAIL or pre-seeding a PASS for a gate that has not run yet. Either
# way it forges the exact signal the runner exists to produce.
#
# Defense under test: the runner records each key exactly once, so any duplicate
# key means a second writer touched the table, and every decision path calls
# assert_gate_table_intact first and fails closed.
set -euo pipefail
TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(cd "$TESTS_DIR/.." && pwd)"
pass=0
fail=0
check() { # check <name> <expected: ok|err> <actual-rc>
local name="$1" expect="$2" rc="$3" got="ok"
[ "$rc" -eq 0 ] || got="err"
if [ "$got" = "$expect" ]; then
pass=$((pass + 1))
else
echo "FAIL: $name (expected $expect, got $got, rc=$rc)"
fail=$((fail + 1))
fi
}
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
mkdir -p "$tmp/artifacts" "$tmp/state"
export WORKSPACE_DIR="$tmp"
export ARTIFACTS_DIR="$tmp/artifacts"
export STATE_DIR="$tmp/state"
export GATE_STATUS_FILE="$tmp/state/gate-status.tsv"
# shellcheck source=../scripts/lib.sh
source "$REPO_DIR/scripts/lib.sh"
# 1. An untampered table passes.
record_gate frontend.install PASS "log"
record_gate frontend.lint FAIL "exit 1"
rc=0; ( assert_gate_table_intact ) >/dev/null 2>&1 || rc=$?
check "clean table passes integrity check" ok "$rc"
# 2. Pre-seeding: PR code writes a forged PASS for a gate that has not run yet,
# then the runner records the genuine FAIL. First-wins would return the
# forged PASS, so the duplicate must be detected.
printf 'frontend.build\tPASS\tforged by PR code\n' >>"$GATE_STATUS_FILE"
record_gate frontend.build FAIL "exit 2"
rc=0; ( assert_gate_table_intact ) >/dev/null 2>&1 || rc=$?
check "pre-seeded forged PASS is detected" err "$rc"
# 3. The same tampered table must block a review decision.
cp "$TESTS_DIR/fixtures/artifacts/frontend-pr.json" "$ARTIFACTS_DIR/"
rc=0
env -i PATH="$PATH" HOME="$HOME" \
WORKSPACE_DIR="$tmp" ARTIFACTS_DIR="$tmp/artifacts" STATE_DIR="$tmp/state" \
REVIEW_TYPE=frontend \
"$REPO_DIR/scripts/validate-review-output.sh" \
"$TESTS_DIR/fixtures/reviews/valid-approve.md" >/dev/null 2>&1 || rc=$?
check "tampered table blocks review validation" err "$rc"
# 4. run_gate strips the Actions runner-command channels from the child
# environment, so PR code cannot poison later steps via $GITHUB_ENV.
probe="$tmp/probe.sh"
cat >"$probe" <<'PROBE'
#!/usr/bin/env bash
[ -z "${GITHUB_ENV:-}" ] || { echo "GITHUB_ENV leaked"; exit 1; }
[ -z "${GITHUB_PATH:-}" ] || { echo "GITHUB_PATH leaked"; exit 1; }
[ -z "${RUNNER_TEMP:-}" ] || { echo "RUNNER_TEMP leaked"; exit 1; }
[ -z "${GATE_STATUS_FILE:-}" ] || { echo "GATE_STATUS_FILE leaked"; exit 1; }
exit 0
PROBE
chmod +x "$probe"
rc=0
GITHUB_ENV="$tmp/ghenv" GITHUB_PATH="$tmp/ghpath" RUNNER_TEMP="$tmp" \
run_gate probe.env probe.log "$probe" >/dev/null 2>&1 || rc=$?
check "run_gate strips runner-command channels from PR code" ok "$rc"
echo "gate-integrity: $pass passed, $fail failed"
[ "$fail" -eq 0 ]