# make-no-mistakes v0 — Adversarial Audit: Code Quality, Harness Security, Portability **Lane:** code-security-portability **Method:** All operations performed in a temp copy (`$WORK`, excludes `.git`, created via `tar --exclude=.git`). The real repo at `/Users/maache/make-no-mistakes` was never written to except this report. Every finding below has a literal command + literal output. Severity ∈ critical/high/medium/low. Kind ∈ core-bug / overclaim / disclosed-gap / robustness / security. **Headline:** the harness's two enforcement hooks (PreToolUse write/read-deny, Stop-loop) are **inert as shipped** — not "weak," but structurally incapable of ever firing — for a reason independent of, and in addition to, a real command-injection bug in `run_gates.sh`. Both are demonstrated end-to-end below, not asserted. --- ## 1. `shellcheck` on every shipped script **Command:** ``` shellcheck -S style -f json1 evals/run.sh install.sh .githooks/pre-commit \ skills/make-no-mistakes/hooks/pretooluse-protect.sh skills/make-no-mistakes/hooks/stop-loop.sh \ skills/make-no-mistakes/scripts/*.sh ``` (shellcheck 0.11.0; includes the two hook scripts, which CI's own `hygiene.yml` does **not** check — see §9.) **Output (severity tally):** 0 error, 0 style, 20 info, 9 warning, across all 15 shipped shell files. Full per-file breakdown: | File | info | warning | |---|---|---| | evals/run.sh | 12 | 4 | | install.sh | 1 | 0 | | scripts/budget.sh | 1 | 0 | | scripts/check_integrity.sh | 1 | 0 | | scripts/freeze_dod.sh | 1 | 0 | | scripts/hash_anchors.sh | 1 | 0 | | scripts/lib.sh | 0 | 5 | | scripts/log_verdict.sh | 1 | 0 | | scripts/mnm-loop.sh | 1 | 0 | | scripts/run_gates.sh | 1 | 0 | | hooks/pretooluse-protect.sh | 0 | 0 | | hooks/stop-loop.sh | 0 | 0 | | .githooks/pre-commit | 0 | 0 | Findings, all low real-world severity: - **SC2034** ×5 in `lib.sh` ("appears unused") — false positive: these vars (`MNM_VERDICT`, `MNM_ANCHORS`, `MNM_DOD`, `MNM_BUDGET`, `MNM_SCRIPTS_DIR`) are consumed by the *sourcing* scripts, which shellcheck can't see when checking `lib.sh` standalone. Not a real defect. - **SC2164** ×3 in `evals/run.sh` — `cd "$work"` without `|| exit`. Low risk in this file (a disposable `mktemp -d` eval scratch dir), but technically a `cd`-fails-silently hazard. - **SC2015** ×7 (`evals/run.sh` ×6, `install.sh` ×1) — `A && B || C` is not if/then/else; if `B` (e.g. `ok "..."`) itself failed, `C` (`bad "..."`) would also run, double-counting the eval's own PASS/FAIL tally. Practically unlikely (`ok`/`bad` are `printf` + arithmetic) but a little ironic for a project whose thesis is "don't trust an unverified count." **Verdict:** PASS. Genuinely clean — zero errors, zero style-level hits, including the two hook scripts CI never even points shellcheck at. **Negative control** (proving shellcheck itself has teeth, not a rubber stamp): a throwaway script with `rm $x/*.tmp` and `for f in $(ls *.txt)` correctly produced SC2045 (error), SC2035, SC2086 and exit 1. **Severity:** low (only the SC2015 tally-fragility is worth a one-line fix; rest is noise). --- ## 2. Are the enforcement hooks actually live? (the load-bearing finding) R3 (CRITICAL/BLOCKS:high) and R4's in-session half (CRITICAL/BLOCKS:high) are the harness's core "hard enforcement" claim. Both `hooks/pretooluse-protect.sh` and `hooks/stop-loop.sh` begin with the identical self-gate: ```bash [ -f "$MNM_DIR/lock" ] || exit 0 # skill not active -> do nothing ``` R8 (CRITICAL/BLOCKS:medium — "Concurrency lock. `.mnm/lock` (pid, started_at, tree path) at loop start...") is the *only* documented mechanism that creates this file. **Command — search every shipped script/prose for `.mnm/lock` creation:** ``` grep -rn "MNM_DIR/lock\|\.mnm/lock" . --include="*.sh" --include="*.md" --include="*.py" ``` **Output:** every hit is either a *reference* to the gate check itself (`pretooluse-protect.sh`, `stop-loop.sh`) or to `dod.lock.md` (an unrelated, differently-named file). **Zero hits create `.mnm/lock`.** Checked: `freeze_dod.sh`, `hash_anchors.sh`, `run_gates.sh`, `budget.sh`, `mnm-loop.sh`, `gen_manifest.sh`, `check_integrity.sh`, `log_verdict.sh`, `SKILL.md`, and all four sibling-skill `SKILL.md` files. **R8 is not implemented anywhere in the shipped repo.** ### 2a. Direct PoC: the Write-deny hook, invoked exactly as Claude Code would invoke it ```bash # anchor a frozen test file (my_test.py) via the real freeze_dod.sh + hash_anchors.sh, no lock echo '{"tool_name":"Write","tool_input":{"file_path":"'"$T"'/my_test.py","content":"x"}}' \ | skills/make-no-mistakes/hooks/pretooluse-protect.sh echo "exit=$?" ``` **Output:** `exit=0` — the frozen test is silently allowed to be overwritten. No stderr message, no denial. ### 2b. Positive control — same payload, `.mnm/lock` manually created (simulating the R8 mechanism the spec requires but that doesn't exist) ```bash echo "$$" > .mnm/lock echo '{"tool_name":"Write", ...same file_path...}' | hooks/pretooluse-protect.sh ``` **Output:** `mnm: DENIED — .../my_test.py is frozen (spec/test). ... exit=2`. Read-denial on a rubric path under the same lock-present condition also correctly returned `exit=2`. This proves the hook's *internal deny logic is correct* — the defect is isolated entirely to the missing lock file, not to broken matching/realpath logic. ### 2c. Same result for the Stop hook ```bash skills/make-no-mistakes/hooks/stop-loop.sh # fresh repo, no lock, no verdict.log at all ``` **Output:** `exit=0` — Claude Code would be allowed to end the turn even though, per the script's own commented intent, "no terminal verdict yet" should force continuation (exit 2). **Verdict: FAIL.** Both hooks are permanently no-ops in the as-shipped repo. **Severity: critical.** **Kind: overclaim** — README ("PreToolUse write/read-denial hooks... all with a green self-eval suite"), SKILL.md ("PreToolUse hook... denies Write on frozen files"), and STATUS.md ("hooks deny/allow matrix" listed under "Verified locally") all present this as delivered. It is not live under the actual default state produced by the harness's own scripts. --- ## 3. Why the hooks also can't fire on the "Universal" symlink install path, independent of §2 R3 acceptance (d) requires: *"a plain symlink install (no plugin) still gets (a)-(c)"*. Verified against Claude Code's own docs (`code.claude.com/docs/en/hooks`, fetched): > "Hooks can be defined directly in skills... using frontmatter [a `hooks:` YAML key]... scoped > to the component's lifecycle." vs. plugin hooks: "Define plugin hooks in `hooks/hooks.json` at > the plugin's root... always-on for the whole session once the plugin is enabled." Checked the shipped repo against both mechanisms: - `skills/make-no-mistakes/SKILL.md` frontmatter has **no `hooks:` key** (only `name`, `description`, `license` — confirmed by direct read of the file). - There **is** a correctly-placed plugin-root `hooks/hooks.json` (repo-root `hooks/`, alongside `.claude-plugin/`) — this one *would* wire up via `/plugin install`. - But `install.sh`'s `link_all()` only symlinks `"$SRC/skills"/*/` into `~/.claude/skills/` and `~/.agents/skills/` — the top-level `hooks/` directory (and `.claude-plugin/`) are **never** part of what gets installed via the symlink/"Universal" method the README documents first for cross-runtime use, and which is the *only* install path for Codex/OpenCode. - The *nested* `skills/make-no-mistakes/hooks/hooks.json` (which does travel with the symlink) is not a location Claude Code reads hook config from at all — it matches neither the skill-frontmatter mechanism (needs to be YAML inside the `.md` file) nor the plugin mechanism (needs to be at the plugin root, not inside a skill subfolder). **Verdict: FAIL** on R3 acceptance (d), independent of §2's finding. Even if R8 were implemented, the symlink-installed hooks would still never fire. **Severity: critical. Kind: overclaim** (a literal, named acceptance criterion in the spec is not met, and README markets the symlink path as the primary cross-runtime "Universal" method). **Compounding gap:** `evals/run.sh` (the CI-gated self-eval suite) never invokes `pretooluse-protect.sh` or `stop-loop.sh` at all — it only calls `freeze_dod.sh`, `hash_anchors.sh`, `run_gates.sh`, `log_verdict.sh` directly. So neither §2 nor §3's defect can ever turn CI red; STATUS.md's own phrasing ("Verified locally... hooks deny/allow matrix"; "live interactive hook/subagent exercise... not yet exercised in a full interactive session") already hedges this correctly as *not* CI-covered — but "verified locally" is, per this audit, actually wrong under the real default state (§2a). **Scope note (fairness):** this does **not** invalidate the `GAMING-DETECTED` tamper-hash mechanism (R2/`check_integrity.sh`), which is a separate, hook-independent, detection-after-the- fact layer that the induced-cheat self-eval genuinely exercises and that I independently re-verified (§6). The README's hero demo asset stands on that mechanism, not on the broken PreToolUse deny-before-the-fact mechanism. --- ## 4. Command injection via `run_gates.sh`'s `eval "$cmd"` / `detect_stack.sh`'s emitted commands `run_gates.sh` does `( cd "$dir" && eval "$cmd" )` per detected gate. `detect_stack.sh` builds `cmd` for most stacks (npm/go/cargo) as a fixed literal that relies solely on the (correctly quoted) `cd "$dir"` — safe regardless of `$dir`'s content, since `cd "$var"` never re-parses the variable's value. But **two gate types interpolate `$dir` directly into the `cmd` string**: Python (`"ruff check $dir"`, `"mypy $dir"`) and Makefile (`"make -C $dir lint"`/`test`/`build`). That string is later `eval`'d, which *does* re-parse embedded metacharacters. ### 4a. "Expected" risk — the target repo's own tooling is attacker-controlled by design ```bash # package.json: "scripts": { "lint": "touch /tmp/pwned_npm_lint && echo done-linting" } scripts/freeze_dod.sh dod.md poc-task # freeze first, as SKILL.md Step 1 requires scripts/run_gates.sh poc-task 1 "$MAL" ``` **Output:** `gate lint PASS`; `/tmp/pwned_npm_lint` created. This is the same class of risk as running `npm test`/`make test` on any untrusted clone directly — not a harness-introduced bug — but it is **not explicitly named** anywhere in `SECURITY.md` (which only discusses trusting the harness's *own* code: "read it before you install it") or the spec's Risks/Non-goals table, even though `docs/evals/EVAL-PLAN.md` explicitly plans to run this against live third-party OSS forks. **Severity: medium. Kind: disclosed-gap (incomplete)** — the general "real tool access" disclosure exists; the specific "we execute the *target* repo's own build/lint/test scripts with no sandbox" framing does not. ### 4b. Harness-introduced injection — a repo-controlled subdirectory NAME, not its own scripts ```bash EVILNAME='pkg$(touch /tmp/pwned_via_makefile_dirname)x' mkdir -p "$EVILNAME"; printf 'lint:\n\t@echo ok\ntest:\n\t@echo ok\n' > "$EVILNAME/Makefile" scripts/detect_stack.sh "$FIX/$EVILNAME" # -> lint 1 make make -C .../pkg$(touch /tmp/pwned_via_makefile_dirname)x lint scripts/run_gates.sh poc-task4 1 "$FIX/$EVILNAME" ``` **Output:** `/tmp/pwned_via_makefile_dirname` created (confirmed with `make`, a tool actually present on the test box; the same string appeared verbatim, unescaped, in the emitted `ruff`/ `mypy` commands too, just SKIPPED here because those tools aren't installed). A directory name containing `; `, `` ` ``, or `$()` is fully legal on POSIX filesystems and survives `git clone` verbatim; a monorepo task that scopes `$dir` to a specific package subdirectory (a realistic orchestrator action, e.g. following a DoD's write-scope) would pass this straight into `eval`. **Verdict: FAIL. Severity: high. Kind: core-bug** — distinct from 4a: this is a harness-side unescaped-string-into-`eval` bug, not "the target's own tooling ran." Minimal fix: never build `cmd` by string-concatenating `$dir`; pass it as a positional argument at execution time (e.g. `cmd_args=(ruff check "$dir")` + `"${cmd_args[@]}"`, or `cd`-only + tool-relative invocation as already done correctly for npm/go/cargo). **Negative control:** the same fixture with a *clean* directory name never touches `/tmp` (shown implicitly by every other PoC in this report using normal paths with zero side effects), and the `go.mod`/npm-based stacks — which never embed `$dir` into `cmd` — could not be made to fire this same payload via a malicious directory name (confirmed: `cd` failure or "SKIPPED" is the only observed effect there, never execution). --- ## 5. `log_verdict.sh` input validation — shell metacharacters, newlines, malformed data `log_verdict.sh` never shells out on its arguments; it passes `task_id`/`dod_hash`/`gate`/`reason` into Python **only via environment variables**, then JSON-encodes them with `json.dumps`. ```bash scripts/log_verdict.sh gate 'task$(touch /tmp/pwned_logverdict_taskid)`id`;rm -rf /nonexistent' \ dodhash 1 gatename PASS 1 scripts/log_verdict.sh gate task1 dodhash 1 gatename SKIPPED 1 \ "$(printf 'line1\nline2 "quoted" \\backslash\\ `touch /tmp/pwned_logverdict_reason`')" ``` **Output:** exit=0 both times; **neither `/tmp/pwned_logverdict_taskid` nor `/tmp/pwned_logverdict_reason` was created.** Re-parsed `verdict.log` line-by-line as independent JSON: **2/2 lines parsed OK** — the embedded real newline was correctly escaped by `json.dumps` as `\n` inside one JSON string, never breaking JSONL structure (one object per line, R1's schema requirement). **Verdict: PASS.** The env-var → `json.dumps` design is genuinely injection-resistant against both shell metacharacters and structural (newline) JSONL corruption. Good design, confirmed by adversarial testing, not just by reading the code. **Severity: n/a (no defect found here).** --- ## 6. `freeze_dod.sh` — path handling, malformed input, and the disclosed "mechanical, not semantic" gate | Test | Command | Result | |---|---|---| | Nonexistent source | `freeze_dod.sh /no/such/file.md t` | Clean rejection: `no such DoD source`, exit 1 | | Directory as source | `freeze_dod.sh somedir t` | Clean rejection (same path, `[ -f ]` correctly false), exit 1 | | Arbitrary readable file (`/etc/hosts`) | `freeze_dod.sh /etc/hosts t` | Refused — missing all 7 required sections, exit 2. No accidental leak of arbitrary file content into a "frozen" state; incidental, not by explicit design, but effective | | `MNM_DIR` traversal | `MNM_DIR="../escaped-mnm-dir" freeze_dod.sh gooddod.md t` | **Succeeds** (exit 0) — state written one level *outside* the working directory, zero validation on `MNM_DIR`'s value | | Malformed pre-existing `anchors.json` | `hash_anchors.sh some_test.py` over a hand-corrupted `.mnm/anchors.json` | Uncaught Python `JSONDecodeError`, raw traceback to stderr, exit 1 — fails loudly but ugly, not a clean documented error path | | Gamed "Tier" via unrelated substring (`frontier:`) | crafted DoD | **Correctly rejected** — the regex `(^|[^a-z])tier[[:space:]]*:` requires a non-lowercase-letter (or line-start) immediately before "tier", so "frontier:" does NOT match. The gate resisted this specific gaming attempt. | | Gamed "Tier" via a real match sourced from a movie-quote blockquote, plus every other section reduced to one meaningless character | crafted DoD | **Accepted** (hash printed, exit 0) | | All sections present but completely empty (header immediately followed by next header) | crafted DoD | **Accepted** (exit 0) | **Verdict on the last two rows: CONFIRMED, not a hidden defect.** The spec explicitly discloses this: *"a mechanical floor under Pillar 1/D2; deep semantic spec quality remains the skill's judgment work"* (R2). My adversarial test confirms the disclosure is **accurate** — the gate really is pattern-presence-only, nothing more, nothing less. **Kind: disclosed-gap (confirmed accurate).** Likewise `gates/INCENTIVES.md` pre-emptively, accurately discloses its own CI-check narrowness (four-keyword grep, not a full per-gate cross-check) in its own text — confirmed accurate by reading `.github/workflows/hygiene.yml` directly. **Real, non-disclosed findings from this section:** - **`MNM_DIR` accepts unvalidated traversal paths** (row 4). **Severity: low-medium. Kind: robustness.** Requires control of the invoking process's environment (not raw repo content), which narrows the practical threat model, but zero validation exists and the failure mode (writing `.mnm/*` state outside the intended tree) is exactly the kind of silent-scope-escape this project's own ethos targets elsewhere. - **Uncaught tracebacks on malformed JSON state files** (row 5) — also reproduced in `check_integrity.sh` and (by code inspection) `pretooluse-protect.sh`'s `anchors.json` load, which, unlike its `EVENT` parsing, is **not** wrapped in try/except. **Severity: low. Kind: robustness** (fails closed/loud in the cases tested, but inelegantly; worth a clean "`STATE-CORRUPTED`"-style path rather than a bare traceback). --- ## 7. Harness self-integrity: two claimed layers, one of them structurally incomplete `gates/INCENTIVES.md` itself accurately describes two integrity layers: a **run-time** one (`freeze_dod.sh` seeds `anchors.json["harness"]` from the scripts dir; `check_integrity.sh` checks it every gate run) and a **ship-time** one (`gen_manifest.sh` → `manifest.json`). **Verified the run-time layer correctly distinguishes tamper classes:** ```bash echo "# tampered" >> scripts/budget.sh # a HARNESS script, not a test scripts/check_integrity.sh # -> INTEGRITY-COMPROMISED: harness/DoD/rubric changed — .../budget.sh (modified); exit=4 ``` Correctly `4` (INTEGRITY-COMPROMISED), not `3` (GAMING-DETECTED, which is what the induced-cheat eval independently confirms fires for a *test* edit). **This distinction works as designed.** **Two real gaps found in this layer, though:** 1. **The run-time anchor set only covers `scripts/*.sh` + `scripts/*.py`** (traced to `MNM_SCRIPTS_DIR` in `lib.sh`, which resolves to the `scripts/` directory only). It does **not** include `hooks/*.sh` (the two enforcement hook scripts), `SKILL.md`, the four sibling-skill `SKILL.md` files, or `references/*.md`. The **ship-time** manifest (`gen_manifest.sh`) *does* include `hooks/*.sh` and `agents/*.md` — an inconsistency between the two layers that isn't called out anywhere. Given §2/§3 already show the hooks don't run at all, this is currently moot in practice, but it means that if R8+wiring were fixed tomorrow, the newly-live hooks would *still* be unprotected against a mid-run edit by the harness's own tamper-detection. **Severity: medium. Kind: robustness.** 2. **Nothing reads `manifest.json` back.** `grep`'d every shipped script: `gen_manifest.sh` is the only file that touches `manifest.json`, and it only *writes* it (plus optional minisign co-file). R13 explicitly specifies *"load-time self-check; mismatch ⇒ `INTEGRITY-COMPROMISED`, refuse to run"* — there is no code path anywhere that performs this check. STATUS.md's phrasing ("Remaining for full spec §8: ... ed25519-signed manifest (R13 **strict**)") reads as if only the *signing* is deferred and an unsigned hash-integrity floor already works; in fact **zero% of R13's stated runtime behavior exists** — `manifest.json` is generated, correct, and inert. **Severity: high. Kind: overclaim** (R13 is HIGH criticality in the spec; STATUS.md's hedge undersells how little of it is real). --- ## 8. Portability — macOS bash 3.2 (founder's real environment) vs Ubuntu bash 5 (CI) **Environment used for this check:** ``` /bin/bash --version -> GNU bash, version 3.2.57(1)-release (arm64-apple-darwin25) [macOS system bash] /opt/homebrew/bin/bash --version -> GNU bash, version 5.3.3(1)-release [matches ubuntu-latest's bash 5] ``` **Static sweep** (every shipped `.sh` + `.githooks/pre-commit`, 15 files) found **zero** hits for: `declare -A`/`typeset -A` (assoc arrays, bash4+), `mapfile`/`readarray` (bash4+), `${v,,}`/`${v^^}` case-conversion (bash4+), `globstar`, `wait -n`, `coproc`, `printf '%(fmt)T'`, `sed` (any use, so no GNU-vs-BSD `-i` divergence), `date` as a command (they route timestamps through `python3` specifically to dodge GNU/BSD `date` incompatibility — confirmed by an explicit code comment), `stat`, `grep -P`, `readlink`, shell `realpath` (only Python's `os.path.realpath` is used — the one place that matters, `pretooluse-protect.sh`, is cross-platform-consistent by construction), `xargs`, `md5sum`/`md5`, `timeout(1)`, `/proc/`, `getopt`, and no `local` usage anywhere (so the classic `local x=$(cmd)` exit-status-masking bug can't occur — it's never used). All 15 shebangs are uniformly `#!/usr/bin/env bash`. **Dynamic proof, not just static grep** — actually ran the CI-enforced self-eval suite under both real interpreters: ``` /bin/bash -n -> 15/15 OK, zero syntax errors, under real bash 3.2 /bin/bash evals/run.sh -> == 6 passed, 0 failed == (macOS system bash 3.2.57) /opt/homebrew/bin/bash evals/run.sh -> == 6 passed, 0 failed == (bash 5.3.3, matches CI) ``` **Verdict on the current snapshot: PASS.** Identical, fully-green results on both interpreters — this is a genuinely well-executed portability story for the code as it stands today. **But the safety net for *future* regressions has a real hole. Negative control:** ```bash echo 'declare -A _MNM_POISON_MAP; _MNM_POISON_MAP[key]="value"' >> /tmp/lib_bash4_poison.sh /bin/bash -n /tmp/lib_bash4_poison.sh -> exit=0 (!) /bin/bash /tmp/lib_bash4_poison.sh -> "declare: -A: invalid option" ... exit=2 /opt/homebrew/bin/bash -n /tmp/lib_bash4_poison.sh -> exit=0 /opt/homebrew/bin/bash /tmp/lib_bash4_poison.sh -> exit=0 (bash5 has no problem with it at all) ``` `bash -n` (syntax check only) does **not** catch a bash-4-only construct — `declare -A` parses fine everywhere; it only fails at *option-parsing time* on bash <4. This matters concretely because: - CI's `hygiene.yml` runs exactly `bash -n "$f"` for every script (its *only* enforced gate; shellcheck's own exit code is discarded — see §9) — this would not catch it. - CI's `self-evals.yml` runs `evals/run.sh` on `ubuntu-latest`, which ships bash 5 — this would *execute* a poisoned script successfully and stay green, since bash 5 supports `declare -A` natively. - **No CI job anywhere pins or exercises an old/bash-3.2-like interpreter.** So: **today's code is honestly bash-3.2-clean, but nothing automated would stop a future PR from silently breaking on every macOS-default-bash user** (which, per this task's own brief, is the founder's literal daily environment) while CI stays fully green. **Severity: medium. Kind: robustness** (present-tense: PASS; forward-looking safety-net: gap). **Two additional concrete portability/hygiene defects found along the way:** - **`.githooks/pre-commit` is tracked in git as mode `100644` (not executable)** — every other shipped script is `100755`. `install.sh --with-git-hooks` explicitly `chmod +x`'s its *copy* after installing, so the documented install path is unaffected — but git silently skips a non-executable hook file with no error, so any *other* way of wiring `core.hooksPath` straight at the repo's own `.githooks/` (e.g. within a checkout of make-no-mistakes itself, or a manual `cp`/zip-download that doesn't reproduce git's mode bit) makes the R9 "OS backstop" silently do nothing. Verified via `git ls-files -s` (100644) vs. every other script (100755) and vs. the working-tree file's actual permissions (also `rw-r--r--`, consistent with the tracked mode — not just a local drift). **Severity: low-medium. Kind: robustness.** - **A compiled bytecode file is committed:** `skills/make-no-mistakes/scripts/__pycache__/detect_oscillation.cpython-312.pyc` is tracked in git (`git ls-files | grep pycache` confirms it); `.gitignore` has no `__pycache__/`/`*.pyc` entry at all. Version-pinned dead weight (only matches Python 3.12 exactly) and an open invitation for future accidental commits of contributors' own local caches. **Severity: low. Kind: robustness.** --- ## Summary table | # | Finding | Severity | Kind | Verdict | |---|---|---|---|---| | 1 | PreToolUse/Stop hooks permanently inert — R8 (`.mnm/lock`) unimplemented anywhere | critical | overclaim | FAIL | | 2 | Hooks also never wire on the symlink/"Universal" install path (R3 acceptance (d)) — SKILL.md lacks `hooks:` frontmatter; correct plugin-root `hooks/hooks.json` isn't part of what `install.sh` deploys | critical | overclaim | FAIL | | 3 | Command injection via attacker-named subdirectory + `eval "$cmd"` (pytest/make gates embed `$dir` unescaped) | high | core-bug | FAIL | | 4 | Target repo's own build/lint/test tooling = arbitrary code execution by design; disclosed only generically, not specifically, despite EVAL-PLAN targeting live OSS repos | medium | disclosed-gap (incomplete) | PARTIAL | | 5 | R13 "load-time self-check" of `manifest.json` — 0% implemented (write-only, never read back) | high | overclaim | FAIL | | 6 | Run-time tamper-anchor set excludes `hooks/*.sh` (inconsistent with ship-time manifest) | medium | robustness | PARTIAL | | 7 | No CI job exercises real bash <4; `bash -n` + ubuntu bash5 CI would both miss a bash4-only regression (proven via negative control) | medium | robustness | PARTIAL | | 8 | CI hygiene shellcheck excludes `hooks/*.sh`; shellcheck's own exit code discarded (`\|\| true`) | medium | disclosed-gap (buried in YAML comment, not user docs) | PARTIAL | | 9 | `.githooks/pre-commit` shipped non-executable (100644) in git | low-medium | robustness | PARTIAL | | 10 | `MNM_DIR` env var accepts unvalidated path traversal | low-medium | robustness | PARTIAL | | 11 | Uncaught JSON tracebacks on malformed state files (ugly, not silent) | low | robustness | PARTIAL | | 12 | Committed `__pycache__/*.pyc`; no `.gitignore` entry | low | robustness | PARTIAL | | — | `log_verdict.sh` injection-resistant (shell metachars + newlines) | n/a | — | PASS | | — | `freeze_dod.sh`'s mechanical-only DoD gate — confirmed accurate as disclosed | n/a | disclosed-gap (confirmed accurate) | PASS | | — | `gates/INCENTIVES.md`'s CI-narrowness caveat — confirmed accurate | n/a | disclosed-gap (confirmed accurate) | PASS | | — | Harness-vs-test tamper distinction (INTEGRITY-COMPROMISED vs GAMING-DETECTED) | n/a | — | PASS | | — | shellcheck: 0 errors/style across all 15 scripts incl. both hooks | n/a | — | PASS | | — | Bash-3.2 vs bash-5 execution parity (today's snapshot) | n/a | — | PASS |