Commit Graph

3 Commits

Author SHA1 Message Date
FilipBl4gojevic
97987427ce fix(signer): close two review-found gaps in ownAddresses validation
Fix 1 (Important, live bypass): the blank-address fix from the prior commit
used .trim().length > 0, which only strips the ECMAScript WhiteSpace set
(NBSP U+00A0, BOM U+FEFF, etc). U+200B ZERO WIDTH SPACE and U+2060 WORD
JOINER are Unicode category Cf (format), not Zs, so trim() leaves them
untouched - ownAddresses: ['\u200B'] passed the filter, bypassed the
fail-closed guard, and reproduced allowed:true against the shipped code
(verified directly, not just against a mutant). Replaced the blank-only
filter with a positive check: an entry only counts as a known own address if
it starts with 'addr' after trimming. A denylist of invisible codepoints is
unwinnable; this rules out anything built from them without enumerating any.
Applies at both call sites (evaluate's guard and computeOutflow's Set) via
the shared knownAddresses() helper, as it already did.

Fix 2 (Important, surviving mutant): added a regression test that exercises
computeOutflow's own Set-construction filtering directly, independent of
evaluate's separate guard - a blank/invisible output address paired with a
matching blank/invisible ownAddresses entry must still count as outflow, not
be matched as change. Nothing previously exercised this combination: every
prior blank-entry test either went through evaluate (whose guard would
refuse first) or paired a blank ownAddresses entry with a real output
address. Confirmed this test now fails if computeOutflow's Set reverts to
unfiltered while evaluate's guard stays filtered (re-verified in this
session; see the report addendum for output).

Fix 3: added a doc comment on evaluate() stating plainly that it bounds
lovelace only - assetMovements is reported for visibility and never gates
allowed. No interface change; this is documentation of the existing,
deliberate v1 scope boundary already flagged in the task report, not new
behavior.

Re-ran the full mutation set after all three fixes: inverted classification,
removed fail-closed guard entirely, guard reverted to raw
ownAddresses.length === 0, and computeOutflow's Set left unfiltered are all
caught. Full before/after evidence in the report addendum.
2026-07-28 12:57:45 +02:00
FilipBl4gojevic
9bbd26b04c fix(signer): treat blank-only ownAddresses as no known own address
Adversarial pass on the spend policy found that ownAddresses.length === 0
does not catch a non-empty array containing only blank entries - a realistic
outcome of ''.split(',') on an empty config/env value, which yields [''] not
[]. A blank entry never matches a real output address, so it degrades to
"every output looks foreign" rather than permitting anything, but it bypassed
the explicit fail-closed path and its message, and if the resulting
over-counted outflow still happened to land under the configured limits the
transaction would be signed without any real own address ever having been
known - exactly the condition that check exists to refuse.

knownAddresses() filters blank entries before both the Set used for
classification and the fail-closed emptiness check, so an all-blank list now
refuses the same way a literally empty one does. A blank entry alongside a
real address is unaffected (regression-tested).

Also adds the rest of the adversarial pass: exact-limit boundaries one
lovelace either side, many-small-outputs summation, aggregation correctness
(duplicate own addresses, repeated foreign addresses, multi-asset and
multi-output asset movements), check-ordering pins, zero-limit and
already-over-budget edge cases, and a pinned note that SpendLimits governs
lovelace only - native asset movements are tracked but not limited.

Every core comparison was verified to actually fail on injection (inverted
own/foreign check, dropped fee, dropped daily history, off-by-one on both
limit comparisons) before being reverted; see the task report.
2026-07-28 12:30:14 +02:00
FilipBl4gojevic
65a3c00994 feat(signer): spend policy over true net outflow
Net outflow is every output that does not return to us, plus the fee - more
accurate than summing declared amounts, and computable without chain access.
Refuses when no own address is known, since change cannot then be identified.
2026-07-28 12:23:12 +02:00