guard: close four fail-open holes found by independent review
ci/woodpecker/pr/ci Pipeline was canceled
ci/woodpecker/pr/ci Pipeline was canceled
An independent reviewer broke all three new controls before they shipped. Every finding is reproduced as a fixture or a repro, because the class is recurring rather than incidental: each hole was a case where the answer was "allow" because something was ABSENT rather than because it was CHECKED. 1. wrapper-guard read only the spellings it knew. `curl -d@body` (no space), `--request=POST` (equals form), and a URL path assembled from shell variables each carried a real provider write straight through. Write detection now covers every body and method form curl accepts, and the endpoint match no longer anchors on a literal host path that a variable can dissolve. 2. wrapper-guard blocked only when the wrapper FILE existed. A host with a broken or partial install therefore permitted exactly the raw writes the guard exists to stop. Blocking is now on the endpoint; a missing wrapper changes the remedy text, not the verdict — a broken install is not permission to bypass gate 7. 3. mosaic-worktree read a worktree's safety from two questions, and a clean, fully-pushed tree holding a gitignored `local.secret` answered both with zero. `git worktree remove` then deleted the one copy in existence. A file is gitignored precisely so nothing else holds it, so ignored-but-not- disposable files are now a third evidence question. Build junk (node_modules, .venv, dist, caches, *.pyc) stays disposable, so the common case still reads SAFE. 4. check-tools-index counted a documented tool as discoverable at mode 0644. Every caller tests `[ -x ]`, so a non-executable tool is a missing tool; it now fails the gate with its own message. Local gates green: sanitization, resident budget, test enumeration, tools-index (4/4 self-test, 100% on the enforced git suite), and wrapper-guard 20/20.
This commit is contained in:
@@ -151,7 +151,7 @@ run_check() {
|
||||
case "$suite" in _*) continue ;; esac
|
||||
|
||||
local total=0 found=0
|
||||
local -a suite_missing=()
|
||||
local -a suite_missing=() suite_noexec=()
|
||||
for tool in "$dir"*.sh; do
|
||||
[ -e "$tool" ] || continue
|
||||
base="$(basename -- "$tool")"
|
||||
@@ -159,6 +159,13 @@ run_check() {
|
||||
total=$((total + 1))
|
||||
if documented "$base"; then
|
||||
found=$((found + 1))
|
||||
# Documented AND present is not enough. The index presents these as
|
||||
# commands to run, and every caller — the wrapper guard included —
|
||||
# decides "is this tool here?" with `[ -x ]`. A 0644 wrapper is
|
||||
# documented, present, and dead: it reads as absent to every check that
|
||||
# matters while scoring 100% here. That is a false green, which is worse
|
||||
# than a red, so it fails rather than warns.
|
||||
[ -x "$tool" ] || suite_noexec+=("$base")
|
||||
else
|
||||
suite_missing+=("$base")
|
||||
fi
|
||||
@@ -166,11 +173,16 @@ run_check() {
|
||||
[ "$total" -eq 0 ] && continue
|
||||
|
||||
local pct=$(( found * 100 / total ))
|
||||
if [ "$enforced" -eq 1 ] && [ ${#suite_noexec[@]} -gt 0 ]; then
|
||||
printf 'FAIL %-12s %3d%% (%d/%d) documented but not executable: %s\n' \
|
||||
"$suite" "$pct" "$found" "$total" "${suite_noexec[*]}"
|
||||
rc=1
|
||||
fi
|
||||
if [ "$enforced" -eq 1 ] && [ ${#suite_missing[@]} -gt 0 ]; then
|
||||
printf 'FAIL %-12s %3d%% (%d/%d) undocumented: %s\n' \
|
||||
"$suite" "$pct" "$found" "$total" "${suite_missing[*]}"
|
||||
rc=1
|
||||
elif [ "$enforced" -eq 1 ]; then
|
||||
elif [ "$enforced" -eq 1 ] && [ ${#suite_noexec[@]} -eq 0 ]; then
|
||||
printf 'ok %-12s %3d%% (%d/%d) [enforced]\n' "$suite" "$pct" "$found" "$total"
|
||||
else
|
||||
printf 'info %-12s %3d%% (%d/%d) not yet enforced\n' "$suite" "$pct" "$found" "$total"
|
||||
@@ -222,6 +234,7 @@ self_test() {
|
||||
mkdir -p "$tmp/tools/git"
|
||||
printf '#!/bin/sh\n' > "$tmp/tools/git/documented-tool.sh"
|
||||
printf '#!/bin/sh\n' > "$tmp/tools/git/test-ignored.sh"
|
||||
chmod +x "$tmp/tools/git/documented-tool.sh" "$tmp/tools/git/test-ignored.sh"
|
||||
|
||||
# run_check reads the TOOLS_DIR / DOCS globals; an array cannot ride in a
|
||||
# command-prefix assignment, so point the globals at the fixture directly.
|
||||
@@ -231,18 +244,18 @@ self_test() {
|
||||
# Case 1: fully documented -> pass.
|
||||
printf 'see tools/git/documented-tool.sh for details\n' > "$tmp/doc.md"
|
||||
if run_check >/dev/null; then
|
||||
printf 'self-test 1/3 ok (complete index passes)\n'
|
||||
printf 'self-test 1/4 ok (complete index passes)\n'
|
||||
else
|
||||
printf 'self-test 1/3 FAIL (complete index should pass)\n'; return 1
|
||||
printf 'self-test 1/4 FAIL (complete index should pass)\n'; return 1
|
||||
fi
|
||||
|
||||
# Case 2: an undocumented tool -> fail.
|
||||
printf '#!/bin/sh\n' > "$tmp/tools/git/undocumented-tool.sh"
|
||||
rc=0; run_check >/dev/null || rc=$?
|
||||
if [ "$rc" -eq 1 ]; then
|
||||
printf 'self-test 2/3 ok (undocumented tool fails the gate)\n'
|
||||
printf 'self-test 2/4 ok (undocumented tool fails the gate)\n'
|
||||
else
|
||||
printf 'self-test 2/3 FAIL (undocumented tool should fail, got rc=%s)\n' "$rc"; return 1
|
||||
printf 'self-test 2/4 FAIL (undocumented tool should fail, got rc=%s)\n' "$rc"; return 1
|
||||
fi
|
||||
|
||||
# Case 3: a stale index reference -> fail.
|
||||
@@ -250,12 +263,26 @@ self_test() {
|
||||
printf 'also tools/git/deleted-tool.sh\n' >> "$tmp/doc.md"
|
||||
rc=0; run_check >/dev/null || rc=$?
|
||||
if [ "$rc" -eq 1 ]; then
|
||||
printf 'self-test 3/3 ok (stale index reference fails the gate)\n'
|
||||
printf 'self-test 3/4 ok (stale index reference fails the gate)\n'
|
||||
else
|
||||
printf 'self-test 3/3 FAIL (stale reference should fail, got rc=%s)\n' "$rc"; return 1
|
||||
printf 'self-test 3/4 FAIL (stale reference should fail, got rc=%s)\n' "$rc"; return 1
|
||||
fi
|
||||
|
||||
printf '\nself-test passed: the gate demonstrably reds on both drift directions.\n'
|
||||
# Case 4: documented, present, and NOT executable -> fail. Found by an
|
||||
# independent reviewer: a 0644 wrapper scored 100% here while reading as
|
||||
# absent to every `[ -x ]` in the fleet, including the wrapper guard's.
|
||||
sed -i '/deleted-tool/d' "$tmp/doc.md"
|
||||
printf '#!/bin/sh\n' > "$tmp/tools/git/noexec-tool.sh"
|
||||
chmod 0644 "$tmp/tools/git/noexec-tool.sh"
|
||||
printf 'and tools/git/noexec-tool.sh\n' >> "$tmp/doc.md"
|
||||
rc=0; run_check >/dev/null || rc=$?
|
||||
if [ "$rc" -eq 1 ]; then
|
||||
printf 'self-test 4/4 ok (documented but non-executable tool fails the gate)\n'
|
||||
else
|
||||
printf 'self-test 4/4 FAIL (non-executable tool should fail, got rc=%s)\n' "$rc"; return 1
|
||||
fi
|
||||
|
||||
printf '\nself-test passed: the gate demonstrably reds on every drift direction.\n'
|
||||
}
|
||||
|
||||
if [ "$SELF_TEST" -eq 1 ]; then
|
||||
|
||||
Reference in New Issue
Block a user