test(security): pin the acknowledgedNonProtocolTarget stamp-writer set by identity (#790) - #826
Merged
Merged
Conversation
#790) Pre-sign block 4b (src/signing/pre-sign-check.ts, #786) refuses every STAMPED transaction to the LiFi Diamond. Its soundness rests on the invariant "only prepare_custom_call both stamps acknowledgedNonProtocolTarget and can target the Diamond" — true today (three writers: prepareCustomCall + two Curve builders whose destinations are stable_ng-factory validated), but the invariant lived only in a source comment. A future stamp writer that legitimately targets the Diamond would be silently OVER-BLOCKED (false refuse). Adds test/790-stamp-writer-set.test.ts: a TypeScript-AST walk of src/ that pins the stamp-writer set BY IDENTITY (file + enclosing function + stamped value) and fails loudly, naming block 4b, when the set changes. The existing #757 U1 guard (test/757-recipient-authorization.test.ts) pins only the writer COUNT with a line regex; it stays untouched. This guard adds what #790 asks for: a count-preserving relocation goes RED, comments/string literals can neither false-positive nor mask a site, and a two-way anchor asserts block 4b itself still keys on the stamp. Test-only change: no production code touched. Closes #790
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #790
What changed
One new test file —
test/790-stamp-writer-set.test.ts. No production code is touched.Pre-sign block
4b(src/signing/pre-sign-check.ts, from #786) refuses every stamped transaction to the LiFi Diamond. Its soundness rests on an invariant that, before this PR, lived only in a source comment:Verified true on live
main— exactly three writers:src/modules/execution/index.ts:1726prepareCustomCallsrc/modules/curve/actions.ts:251buildCurveAddLiquidityget_n_coins > 0) → only ever a Curve poolsrc/modules/curve/actions.ts:469buildCurveSwapIf a future stamp writer is added that legitimately targets the Diamond, 4b silently over-blocks it (a false refuse — availability, not a drain, as #790 states), and nothing mechanical forces anyone to notice.
The new test walks
src/via the TypeScript AST, collects every site that sets the flag (object-literal property, shorthand property, and= / ??= / ||= / &&=assignment to a property/element access named the flag), and pins the discovered set by identity —file :: enclosingFunction = value. Line numbers are deliberately not part of the pinned key, so an unrelated edit above a site cannot turn the guard red. The failure message stops the editor and asks block 4b's actual question ("can this new writer target the LiFi Diamond?"), and states that the answer, if yes, is a narrower discriminator in 4b — never a widened ack.Relation to the existing #757 U1 guard — nothing was weakened
test/757-recipient-authorization.test.ts:511already asserts the writer count is 3, using a line-oriented regex. That test is untouched. This PR adds a separate file rather than folding into it, because the delta #790 asks for is not the count://comment or a string literal that looks like an assignment can neither false-positive nor mask a real site (the regex has no such awareness);No existing assertion was changed, relaxed, or removed.
Falsifier
The pin test (
src/ contains EXACTLY the pinned stamp writers) is the falsifier for the drift #790 describes: add a fourthacknowledgedNonProtocolTarget = trueanywhere undersrc/, or move one of the three into a different function, and it goes RED with the discovered set printed. On unmodifiedmainthe guard does not exist at all, so the drift it catches is currently unguarded (the #757 count check catches only the strict-addition subset).Three supporting tests keep it from passing vacuously or rotting:
src/walk yields > 250.tsfiles (≈318 today) and that at least one file still mentions the flag, so a broken walk or a broken pre-filter cannot silently pin an empty set (mirrorsdemo-sink-gating.structural.test.ts's > 150-tool anchor);x.flag =,x["flag"] =, arrow-function body) plus the shapes that must NOT count (a=== trueread, an interface field, a comment), and asserts the exact five detected writers. If the AST matching silently stops firing, this goes RED before the pin can pass emptily;src/signing/pre-sign-check.tsstill contains thedest.kind === "lifi-diamond" && tx.acknowledgedNonProtocolTarget === truerefusal, so a rework or removal of 4b surfaces here rather than leaving a guard whose rationale has quietly changed.Tests were NOT run locally
There is no Node/npm on the authoring machine, so
npm test,npm run buildandnpm run lintwere not executed here, and no local result is claimed. CI (Build & Test on Node 20 + 22) is the authoritative check. The file was written to match the repo's proven patterns: thesrc/walk andnew URL("../src/", import.meta.url).pathnameresolution copytest/757-recipient-authorization.test.ts; theimport ts from "typescript"+ts.createSourceFile(…, /*setParentNodes*/ true)usage copiestest/support/sink-reachability.ts.tsconfig.jsonexcludestest/, so this file is not part ofnpm run build's typecheck andnpm run lintonly coverssrc.Blast radius
Test-only. No runtime, signing, or tool-surface behaviour changes; nothing imports the new file. Worst case if it is wrong is a red CI check on this PR, not a production defect. Runtime cost is bounded: all
src/files are read, but only the ~7 that literally contain the identifier are parsed.Residual concerns
Object.assign, a computed key held in a variable, or a spread of an object built elsewhere — is not detected. No such form exists insrc/today; the liveness fixture pins the forms that are covered.#757U1 and this guard now overlap on the strict-addition case. Deliberate — I did not touch another issue's guard to de-duplicate. If maintainers prefer one binding, the INCIDENT: pre-sign block 5 checks selector but never arguments — recipient-bearing fns on RECOGNIZED destinations are signable drain paths (Aave/Morpho/Uniswap/Lido) #757 count assertion is the one this subsumes.Security review
This touches fund-safety / signing-gate territory (block 4b is the #786 drain fix). This is NOT self-certified as correct or complete — I am not asserting that the pinned set or the 4b invariant is sound, only that the set matches live
maintoday and is now mechanically bound. It needs maintainer security review, including whether the two Curve writers' factory-validation anchor is the right basis for excluding them from 4b's concern.🤖 Generated with Claude Code