Found by the round-5 independent review of #205 and reproduced there. Non-blocking for that PR, which passed.
The approval window is closed while an attempt holds the claim, and the attempt then fails retryably at the verify stage (src/background/index.js:914). The release path returns the approval to the pool — but the window it would be retried from no longer exists, and nothing else will ever settle it. The dApp's eth_sendTransaction promise stays pending indefinitely.
This is the deliberate safe side of the round-3 trade, not a regression: settling on that path is precisely the fund-loss defect round 3 found, where an in-flight broadcast was reported to the dApp as 4001 User rejected the request. while the transaction actually sent. Liveness was given up to buy that safety. It should not be given up permanently.
Acceptable fix, from the review: re-check the window on release, and settle 4001 if it is gone. That distinguishes "retry is still possible" from "nobody can ever retry this", which the current release path conflates.
Also in scope: three settlement paths with no test coverage
The suite added by #205 covers the window-close, address-switch and reject paths against a claimed approval. Three more route through settleApproval() and are exercised only by the reviewer's throwaway probes:
AUTISTMASK_APPROVAL_RESPONSE, approve polarity
AUTISTMASK_APPROVAL_RESPONSE, reject polarity
the runtime.onConnect port disconnect
All three were verified correct by hand — each is refused against a mid-broadcast claimed approval and the dApp still receives its result — but nothing in the repo would catch a regression. Given that every one of the five review rounds on that PR found a defect in exactly this machinery, they should be pinned.
One tightening, not a defect
A TX_RESPONSE carrying an attacker-paying artifact aimed at a personal_sign or connection approval (neither has txParams) broadcasts nothing — but it fails closed via a TypeError rather than an explicit type check. Worth making explicit so a future refactor cannot turn an incidental throw into a path.
Definition of done
A retryable failure after the approval window has closed settles the dApp promise rather than orphaning it.
A retryable failure while the window is still open still leaves the approval retryable — the round-3 fund-loss path must not reopen.
Tests cover both, plus the three uncovered settlement paths above against a claimed approval.
The approval type is checked explicitly before a TX_RESPONSE is honoured.
TODO.md updated in the same commit.
make check passes.
Found by the round-5 independent review of https://git.eeqj.de/sneak/AutistMask/pulls/205 and reproduced there. Non-blocking for that PR, which passed.
The approval window is closed while an attempt holds the claim, and the attempt then fails **retryably** at the verify stage (`src/background/index.js:914`). The release path returns the approval to the pool — but the window it would be retried from no longer exists, and nothing else will ever settle it. The dApp's `eth_sendTransaction` promise stays pending indefinitely.
This is the deliberate safe side of the round-3 trade, not a regression: settling on that path is precisely the fund-loss defect round 3 found, where an in-flight broadcast was reported to the dApp as `4001 User rejected the request.` while the transaction actually sent. Liveness was given up to buy that safety. It should not be given up permanently.
Acceptable fix, from the review: **re-check the window on release, and settle `4001` if it is gone.** That distinguishes "retry is still possible" from "nobody can ever retry this", which the current release path conflates.
## Also in scope: three settlement paths with no test coverage
The suite added by https://git.eeqj.de/sneak/AutistMask/pulls/205 covers the window-close, address-switch and reject paths against a claimed approval. Three more route through `settleApproval()` and are exercised only by the reviewer's throwaway probes:
- `AUTISTMASK_APPROVAL_RESPONSE`, approve polarity
- `AUTISTMASK_APPROVAL_RESPONSE`, reject polarity
- the `runtime.onConnect` port disconnect
All three were verified correct by hand — each is refused against a mid-broadcast claimed approval and the dApp still receives its result — but nothing in the repo would catch a regression. Given that every one of the five review rounds on that PR found a defect in exactly this machinery, they should be pinned.
## One tightening, not a defect
A `TX_RESPONSE` carrying an attacker-paying artifact aimed at a `personal_sign` or connection approval (neither has `txParams`) broadcasts nothing — but it fails closed via a `TypeError` rather than an explicit type check. Worth making explicit so a future refactor cannot turn an incidental throw into a path.
## Definition of done
- [ ] A retryable failure after the approval window has closed settles the dApp promise rather than orphaning it.
- [ ] A retryable failure while the window is still open still leaves the approval retryable — the round-3 fund-loss path must not reopen.
- [ ] Tests cover both, plus the three uncovered settlement paths above against a claimed approval.
- [ ] The approval type is checked explicitly before a `TX_RESPONSE` is honoured.
- [ ] `TODO.md` updated in the same commit.
- [ ] `make check` passes.
Completed the remaining parts of the definition of done in #391.
The liveness fix this issue asks for — settle 4001 on release when the window a
retry would use is gone — already landed on next with #271, together with tests for the
after-close and still-open cases.
This change makes AUTISTMASK_TX_RESPONSE refuse any approval that is not a
transaction approval, so a reject no longer retires a sign or connection
approval and a signed artifact never runs the broadcast path against one. Tests
pin the three settlement paths named here — the site-connection port's approve,
reject and disconnect — against a transaction approval broadcasting behind them,
plus the new type check against a sign and a connection approval.
Model: opus-4-8
Completed the remaining parts of the definition of done in
https://git.eeqj.de/sneak/AutistMask/pulls/391.
The liveness fix this issue asks for — settle `4001` on release when the window a
retry would use is gone — already landed on `next` with
https://git.eeqj.de/sneak/AutistMask/issues/271, together with tests for the
after-close and still-open cases.
This change makes `AUTISTMASK_TX_RESPONSE` refuse any approval that is not a
transaction approval, so a reject no longer retires a sign or connection
approval and a signed artifact never runs the broadcast path against one. Tests
pin the three settlement paths named here — the site-connection port's approve,
reject and disconnect — against a transaction approval broadcasting behind them,
plus the new type check against a sign and a connection approval.
Model: opus-4-8
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found by the round-5 independent review of #205 and reproduced there. Non-blocking for that PR, which passed.
The approval window is closed while an attempt holds the claim, and the attempt then fails retryably at the verify stage (
src/background/index.js:914). The release path returns the approval to the pool — but the window it would be retried from no longer exists, and nothing else will ever settle it. The dApp'seth_sendTransactionpromise stays pending indefinitely.This is the deliberate safe side of the round-3 trade, not a regression: settling on that path is precisely the fund-loss defect round 3 found, where an in-flight broadcast was reported to the dApp as
4001 User rejected the request.while the transaction actually sent. Liveness was given up to buy that safety. It should not be given up permanently.Acceptable fix, from the review: re-check the window on release, and settle
4001if it is gone. That distinguishes "retry is still possible" from "nobody can ever retry this", which the current release path conflates.Also in scope: three settlement paths with no test coverage
The suite added by #205 covers the window-close, address-switch and reject paths against a claimed approval. Three more route through
settleApproval()and are exercised only by the reviewer's throwaway probes:AUTISTMASK_APPROVAL_RESPONSE, approve polarityAUTISTMASK_APPROVAL_RESPONSE, reject polarityruntime.onConnectport disconnectAll three were verified correct by hand — each is refused against a mid-broadcast claimed approval and the dApp still receives its result — but nothing in the repo would catch a regression. Given that every one of the five review rounds on that PR found a defect in exactly this machinery, they should be pinned.
One tightening, not a defect
A
TX_RESPONSEcarrying an attacker-paying artifact aimed at apersonal_signor connection approval (neither hastxParams) broadcasts nothing — but it fails closed via aTypeErrorrather than an explicit type check. Worth making explicit so a future refactor cannot turn an incidental throw into a path.Definition of done
TX_RESPONSEis honoured.TODO.mdupdated in the same commit.make checkpasses.Completed the remaining parts of the definition of done in
#391.
The liveness fix this issue asks for — settle
4001on release when the window aretry would use is gone — already landed on
nextwith#271, together with tests for the
after-close and still-open cases.
This change makes
AUTISTMASK_TX_RESPONSErefuse any approval that is not atransaction approval, so a reject no longer retires a sign or connection
approval and a signed artifact never runs the broadcast path against one. Tests
pin the three settlement paths named here — the site-connection port's approve,
reject and disconnect — against a transaction approval broadcasting behind them,
plus the new type check against a sign and a connection approval.
Model: opus-4-8