From 72b577eb8550ad7b5c58d869b8eaea93087c4e3b Mon Sep 17 00:00:00 2001 From: mos-dt-0 Date: Sat, 1 Aug 2026 11:32:35 -0500 Subject: [PATCH] =?UTF-8?q?docs(remediation):=20pre-register=20the=20RM-02?= =?UTF-8?q?=20hardening=20re-review=20=E2=80=94=20attack=20the=20FIX,=20no?= =?UTF-8?q?t=20the=20original=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Registered at 32b490a7 before any reviewer read the diff. I verified three of the four attacks are dead by re-running them myself: injecting activationCommit=HEAD now yields "author-controlled activationCommit is forbidden; history boundary is derived from the registry introduction"; the misspelled outputPatern is rejected as an unknown field by the recursive schema; the global cross-commit duplicate check is present. The three criteria are registered with caseRefs 1/3/3. So the AC set does not spend itself re-checking what I already reproduced. Every round on this mission the remediation introduced the next hole — the commit-binding fix shipped a type confusion, the type-strict fix was clean but the registry around it had four defeats — so the value is in whether the NEW mechanism is defeatable. Blocker 1's fix replaced an author-settable field with a derivation, and a derivation has inputs. H1 asks who controls them: can an author move the derived boundary by adding, renaming, deleting-and- recreating or symlinking gates/gates.manifest.json, or by planting a second manifest earlier in history? If so, blocker 1 is relocated rather than fixed. H2-H4 ask what first-parent traversal, shallow clones, and path deletion do — fail closed or silently derive a wrong boundary. S1 asks whether the schema closure is complete or merely enumerated: find a nested object the author did not list and inject an unknown key there. C2 asks whether the new criteria express D-38 and D-40 generally or only their originating instances — a clause that covers only the instance that produced it is not a clause. Co-Authored-By: Claude Opus 5 (1M context) --- docs/remediation/AC-RM02-HARDENING.md | 67 +++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 docs/remediation/AC-RM02-HARDENING.md diff --git a/docs/remediation/AC-RM02-HARDENING.md b/docs/remediation/AC-RM02-HARDENING.md new file mode 100644 index 00000000..5448dad6 --- /dev/null +++ b/docs/remediation/AC-RM02-HARDENING.md @@ -0,0 +1,67 @@ +# RM-02 / PR #1030 — PRE-REGISTERED RE-REVIEW: the four-blocker hardening pass + +**Subject head (exact):** `32b490a712a5e8e77f13c40c60099b51feb38994` +**Supersedes:** the NO-GO at `83d2ecb2` (D-44, four silent-defeat paths). That verdict is VOID. +**Registered by:** `mos-remediation` **before any reviewer read the diff.** **Reviewer:** `rev-974`. + +## Framing — attack the FIX, not the original bug + +Every round on this mission, **the remediation introduced the next hole**: the commit-binding fix +shipped a type confusion; the type-strict fix was clean but the registry around it had four defeats. +So the highest-value checks here are **not** "was each blocker fixed" — the orchestrator already +reproduced three of the four attacks as dead — but **"is the NEW mechanism itself defeatable?"** + +Blocker 1's fix replaced an author-settable field with a **derivation**. A derivation has inputs. **The +question is who controls them.** + +## H — the new derivation (highest value) + +| id | check | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **H1** | `deriveHistoryBoundary` = parent of the first first-parent commit introducing `gates/gates.manifest.json`. **Are its inputs author-controlled?** Can a PR author influence the derived value by adding, moving, renaming, deleting-and-recreating, or symlinking that path — or by adding a SECOND manifest earlier in history? If the derivation is gameable, blocker 1 is not fixed, only relocated. | +| **H2** | **First-parent traversal:** what happens on a merge commit, an octopus merge, a squash, a rebase that reorders, or a branch where the introducing commit is not on the first-parent chain? Does the boundary silently move, or fail closed? | +| **H3** | **Shallow/partial clone:** the branch previously needed an unshallow fix (`e8959975` "unshallow gate replay history"). If history is shallow, does the derivation fail CLOSED or silently derive a wrong/empty boundary? | +| **H4** | **Path deletion:** if `gates/gates.manifest.json` is absent at HEAD, or was deleted and re-added, what is derived? Fail closed, or a boundary that excludes the deletion window? | +| **H5** | Are the **three registered seam controls (HEAD, HEAD^, introduction) genuinely RED-FIRST** — do they fail against the PRE-fix code for their own stated reason? A control that was always green is a regression guard, not a must-fail control (D-8). | + +## S — the recursive schema closure + +| id | check | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **S1** | **Is closure COMPLETE or enumerated?** The author lists the objects they closed. Find one they did **not** — any nested object anywhere in the manifest — and inject an unknown key there. If it is accepted, the fix covers instances, not the class. | +| **S2** | **Wrong-type, not just unknown-key:** `outputPattern` as a number/array/object/null rather than misspelled. Does the closed schema type-check values, or only key names? | +| **S3** | **Empty/degenerate values:** `outputPattern: ""` — does an empty regex match everything and silently neuter the assertion the same way a typo did? The author claims an empty-pattern control; verify it is bound. | +| **S4** | Confirm the registered typo/wrong-type/empty-pattern controls are **genuine red-first** against pre-fix code, and that anything labelled a regression guard is honestly labelled as one. | + +## E — provider evidence (blocker 4) + +| id | check | +| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **E1** | Global validation runs **before** per-commit filtering **on every path**, including the HEAD-only path the author calls out. Is there any code path that reaches per-commit assessment without it? | +| **E2** | **Duplicate identity by another key:** the fix makes pipeline NUMBER globally unique. Can two records still collide on a different identity dimension — same commit + different number, same URL, same step-id — and certify the wrong subject? | +| **E3** | "Exactly one gate-verify step per record" — what if a record has zero, or two with different outcomes? Fail closed? | + +## C — clauses, integrity, CI + +| id | check | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **C1** | The three new criteria (`RM02-EVIDENCE-SUBJECT-BINDING`, `RM02-TYPE-STRICT-SCHEMA`, `RM02-HISTORY-BOUNDARY`) are **checked criteria with bidirectionally bound cases**, not prose. Orchestrator observed caseRefs 1 / 3 / 3 — verify the bindings actually run and can fail. | +| **C2** | **B1/B2 satisfied in substance:** does `RM02-EVIDENCE-SUBJECT-BINDING` express D-38 generally ("does this gate bind its evidence to its subject?") or only the one pipeline-number instance? Same for D-40 vs the one typo instance. **A clause that only covers its originating instance is not a clause.** | +| **C3** | **Nothing weakened:** diff `83d2ecb2` → `32b490a7` removes/relaxes no existing case, test, or assertion. | +| **C4** | Exact-head CI terminal-green, full `-f json`, counts stated **including `clone`**; machine-checked with `verify-terminal-green.py --expect-commit 32b490a712a5e8e77f13c40c60099b51feb38994`. | +| **C5** | `Closes #1029` still present (D-38c). | + +## Reviewer instruction + +Verdict bound to `32b490a712a5e8e77f13c40c60099b51feb38994`, evidence enumerated, posted durably under +your own identity. If a check is unrunnable, **say so** — never substitute a passing variant. + +**H1 and S1 are the two I most want answered**, because they ask whether each fix closed a **class** or +an **instance**. Attack outside this set: four of the last five blockers on this PR came from mutating +something nobody had registered. + +**Environment note:** `pnpm gate:verify` fails on `checkout-preflight` with outcome 42 (bubblewrap +class) in at least one worktree. If you hit it, report it as an environment limitation — do not treat it +as a finding, and do not let it mask one. + +**No one dispatching this review may state a conclusion on an open check (D-39).**