From 999c7c475e7e5384e1b9253053658be84da83574 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Mon, 6 Jul 2026 21:52:10 +0200 Subject: [PATCH] job4: install guard fail-closed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install-plugins.sh: mktemp failure building CFG_SNAPSHOT now aborts the install loudly (err + exit 1) instead of warning and continuing UNGUARDED — a failed guard used to mean CLAUDE.md/.claude/settings.json/ settings.json could be silently rewritten by graphify's installer for the rest of that run. Closes §3.4. Added T5 to lib/tests/curated-config-guard.test.sh: extracts the WIDER header block (GUARDED_CONFIGS through the closing `fi` — the fail-closed logic lives in the top-level if/else, outside restore_curated_configs(), so it needs its own awk range) in a subshell with a stubbed `mktemp` forced to fail; asserts exit 1 and a loud "mktemp failed" message. +2 assertions (4→6). Verified: bash -n clean, shellcheck clean (both files), full `make test` exit 0. --- install-plugins.sh | 5 ++++- lib/tests/curated-config-guard.test.sh | 30 ++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/install-plugins.sh b/install-plugins.sh index 7edd35d..36efd82 100644 --- a/install-plugins.sh +++ b/install-plugins.sh @@ -62,7 +62,10 @@ if [ -n "$CFG_SNAPSHOT" ]; then done trap restore_curated_configs EXIT else - warn "Config guard disabled (mktemp failed) — CLAUDE.md/settings may drift" + err "Config guard could not be created (mktemp failed) — refusing to run" \ + "unguarded: CLAUDE.md/.claude/settings.json/settings.json could be" \ + "silently rewritten by the installer. Fix mktemp/TMPDIR and retry." + exit 1 fi # Read pinned version from plugins.lock.json diff --git a/lib/tests/curated-config-guard.test.sh b/lib/tests/curated-config-guard.test.sh index bff7133..60e888c 100644 --- a/lib/tests/curated-config-guard.test.sh +++ b/lib/tests/curated-config-guard.test.sh @@ -46,7 +46,33 @@ cmp -s "$REPO/.claude/settings.json" "$EXPECT/.claude/settings.json" check T2-untouched-local-settings-unchanged "$?" 0 cmp -s "$REPO/settings.json" "$EXPECT/settings.json" check T3-untouched-settings-unchanged "$?" 0 -[ ! -d "$CFG_SNAPSHOT" ] -check T4-snapshot-dir-removed "$?" 0 +if [ -d "$CFG_SNAPSHOT" ]; then r4=present; else r4=gone; fi +check T4-snapshot-dir-removed "$r4" gone + +# --- T5: mktemp failure -> fail-closed (install-plugins.sh, the header block +# that builds CFG_SNAPSHOT) — refuses to run unguarded instead of warning and +# continuing. Extracted with a WIDER range than the SUT above: this logic +# lives in the top-level if/else, outside restore_curated_configs(). +SUT2="$(mktemp)" +awk '/^GUARDED_CONFIGS=/,/^fi$/' "$INSTALL_SH" > "$SUT2" +ERR5="$(mktemp)" +( + # shellcheck disable=SC2329 # invoked indirectly by the sourced snippet below + mktemp() { return 1; } # force the header's CFG_SNAPSHOT creation to fail + # shellcheck disable=SC2329 + err() { echo "ERR: $*" >&2; } + # shellcheck disable=SC2329 + warn() { echo "WARN: $*" >&2; } + # shellcheck disable=SC2329 + info() { :; } + REPO="$(command mktemp -d)" + # shellcheck source=/dev/null + source "$SUT2" +) >/dev/null 2>"$ERR5" +rc5=$? +check T5-mktemp-failure-aborts "$rc5" 1 +if grep -qi 'mktemp failed' "$ERR5"; then r5msg=yes; else r5msg=no; fi +check T5-mktemp-failure-loud "$r5msg" yes +rm -f "$ERR5" "$SUT2" printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ]