test: drive the EIP-1193 dApp approval round trips in the browser (closes #183) #273

Merged
clawbot merged 1 commits from test/issue-183-dapp-round-trips into next 2026-08-12 13:23:58 +02:00
Collaborator

Closes #183.

Test-only. git diff origin/next --stat touches README.md, TODO.md, tests/e2e/network.js and tests/e2e/run.js and nothing under src/.

What this covers

Ten new tests, taking the Chrome e2e suite from 27 to 37. The route handler serves a local page on https://dapp.e2e.test/; window.ethereum gets there from the shipped MAIN-world content script, and the page's own EIP-6963 announcement is required to be that object by identity, so nothing the fixture could have installed itself satisfies it. eth_chainId is round-tripped through the content script and background before any prompt is driven.

DoD Where
1. local page from the harness, real inpage provider DAPP_HTML in tests/e2e/network.js, test 28
2. eth_requestAccounts / personal_sign / eth_signTypedData_v4 / eth_sendTransaction through real prompts tests 29-36
3. every signature recovered and compared tests 31, 33, 35
4. rejection paths tests 29, 32, 34, 36
5. password never crosses the boundary tests 31, 33, 35, 37
6. demonstrated to fail captured below

Signatures are recovered in the runner with verifyMessage, verifyTypedData and Transaction.from(), against the active address read out of extension storage. The background verifies too; nothing here leans on its verdict. The transaction assertions run against the raw signed bytes captured at eth_sendRawTransaction — the stub records them and answers with the transaction's real hash, because ethers refuses a hash mismatch — and compare signer, recipient, value, call data, chain id, and that the hash the page received is the hash of those bytes. The rejected transaction is required to have put nothing on the wire at all.

The password check wraps chrome.runtime.sendMessage in the approval window before Approve is clicked and requires the resulting AUTISTMASK_SIGN_RESPONSE/AUTISTMASK_TX_RESPONSE record to be present before asserting on it, so it cannot pass by observing nothing. Test 37 re-reads both channels for the whole section and requires all four methods to appear in the page-side record first.

Item 6 — demonstrated to fail

Six runs against deliberately broken variants, all reverted. Captured output:

a. Signature recovered against the wrong address (the recovered value corrupted in run.js):

# personal_sign: recovered=0x00000000000000000000000000000000000000Ff expected=0x55D4d5b0362F423E04c4ACE69B56c01321BCAD91
not ok 31 - personal_sign signs, and the signature recovers to the address (#183)
  the personal_sign signature recovers to 0x00000000000000000000000000000000000000Ff, not to the approved address 0x55D4d5b0362F423E04c4ACE69B56c01321BCAD91
not ok 33 - eth_signTypedData_v4 signs, and the signature recovers (#183)
  the typed data signature recovers to 0x00000000000000000000000000000000000000Ff, not to the approved address 0x55D4d5b0362F423E04c4ACE69B56c01321BCAD91
not ok 35 - eth_sendTransaction signs the approved transaction and broadcasts it (#183)
  the broadcast transaction was signed by 0x00000000000000000000000000000000000000Ff, not by the approved address 0x55D4d5b0362F423E04c4ACE69B56c01321BCAD91
# 33/37 tests passed
# FAILED

b. The password put back in the payloadpayload.password = $("approve-sign-password").value and the same for approve-tx-password in src/popup/views/approval.js, i.e. the shape #157 removed. Caught in four places:

not ok 31 - personal_sign signs, and the signature recovers to the address (#183)
  the password crossed the extension messaging boundary on the personal_sign approval: [{"type":"AUTISTMASK_SIGN_RESPONSE","id":"854ed9ed-110c-460b-98b0-1dc11dea2331","approved":true,"password":"e2e-harness-password","signature":"0x06bea925f6be8d19b474417166d69190c96cb5b50c729e72d389125d3813b2474e5d84819ab0a2eb7d3cb7c5b069fd85dfcc0378184eabc9a12679d70ad2fee01c"}]
not ok 33 - eth_signTypedData_v4 signs, and the signature recovers (#183)
  the password crossed the extension messaging boundary on the eth_signTypedData_v4 approval: [{"type":"AUTISTMASK_SIGN_RESPONSE","id":"4dd8e2bc-ca9d-45c5-af80-202dcd963172","approved":true,"password":"e2e-harness-password","signature":"0xbffb7caa..."}]
not ok 35 - eth_sendTransaction signs the approved transaction and broadcasts it (#183)
  the password crossed the extension messaging boundary on the eth_sendTransaction approval: [{"type":"AUTISTMASK_TX_RESPONSE","id":"33014c7f-ee55-462f-99bb-f9128e046ef2","approved":true,"password":"e2e-harness-password","rawSignedTx":"0x02f8920180843b9aca00852ecc889a0082520894c0ffee0000000000000000000000000000c0ffee872bb2c8eabcc000a0deadbeef0101..."}]
not ok 37 - the password never crossed either boundary in this section (#183)
  the password crossed the extension messaging boundary between the popup and the background: [ ... all three records ... ]
# 32/37 tests passed
# FAILED

c. A provider that resolves instead of rejectingp.resolve(result) unconditionally in src/content/inpage.js. All four rejection paths bind:

not ok 29 - eth_requestAccounts rejected at the prompt returns a rejection (#183)
  eth_requestAccounts rejection resolved instead of rejecting: {"settled":"resolved"}
not ok 32 - personal_sign rejected returns a rejection to the page (#183)
  personal_sign rejection resolved instead of rejecting: {"settled":"resolved"}
not ok 34 - eth_signTypedData_v4 rejected returns a rejection to the page (#183)
  eth_signTypedData_v4 rejection resolved instead of rejecting: {"settled":"resolved"}
not ok 36 - eth_sendTransaction rejected broadcasts nothing (#183)
  eth_sendTransaction rejection resolved instead of rejecting: {"settled":"resolved"}
# 33/37 tests passed

d, e, f. Approval screens displaying something other than what is signed. Three separate runs, because the assertions shadow each other in order — a wrong origin on the sign screen, a wrong message, a wrong value and wrong call data on the transaction screen:

not ok 33 - eth_signTypedData_v4 signs, and the signature recovers (#183)
  the typed data prompt names the wrong origin: "wallet.example.com"

not ok 31 - personal_sign signs, and the signature recovers to the address (#183)
  the sign prompt shows the wrong message: "Sign in to Example"

not ok 35 - eth_sendTransaction signs the approved transaction and broadcasts it (#183)
  the transaction prompt shows the wrong value: "0.0000 ETH"

not ok 35 - eth_sendTransaction signs the approved transaction and broadcasts it (#183)
  the transaction prompt does not show the approved call data: "0x"

Worth knowing when reading those: the suite is stateful, so a failing test leaves an approval window open and the tests after it fail on the debris. The first failure in each block is the one the mutation caused.

What could NOT be driven, and why

  • The browser-action popup itself. src/background/index.js raises the site-connection prompt through chrome.action.setPopup() + chrome.action.openPopup(). Headless Chromium does open that popup, but Playwright never exposes it as a page — it is not in ctx.pages() after 60s, with the action URL confirmed already set. So that one prompt is driven at the URL the extension put on the action: same page, same approval id, same code path, and the route README documents for reopening a pending approval. Whether a real toolbar click puts it on screen is not observable from a headless harness and is not claimed. The sign and transaction prompts need none of this — they open real windows and are driven as-is.
  • A real dApp, with real funds, against a real network. The RPC is stubbed throughout. That remains a human pass before the 1.0.0 tag and is stated as such in README.md and TODO.md.

Two findings, reported rather than fixed

Neither is touched by this PR.

  1. The EIP-1193 error code does not reach the page. The rejection that crosses the boundary carries {"code":4001,"message":"User rejected the request."} and the test asserts that on the wire, but src/content/inpage.js rebuilds it as new Error(error.message), so the calling page catches an Error with no code property — page Error.code=undefined page Error carries a code=false, printed on every run. RULES.md requires "Rejected approvals return EIP-1193 error code 4001", and a dApp checking err.code === 4001 sees undefined. The test records this rather than asserting either way, because asserting the current behaviour would make the gap permanent.
  2. Approve races the popup teardown for a site connection. btn-approve calls runtime.sendMessage() and then window.close() on the next line; closing disconnects the approval port, and the disconnect handler settles a pending site approval as a rejection. Driven in a tab the teardown wins every time — measured: with the close in place the page is told the user rejected, with it deferred the approval goes through. The harness therefore defers window.close() on that one page and closes it itself once the outcome has been observed, which is documented at the call site. This does not apply to the sign or transaction prompts: their disconnect handler deliberately keeps the approval pending. Whether the same ordering holds in a real toolbar popup is not observable here.

Verification

  • make check — green (includes script/test-verify-build: test-verify-build: 18 case(s) passed; prettier --check .: All matched files use Prettier code style!), re-run after the rebase.
  • make test-e2e# 37/37 tests passed, re-run after rebasing onto 52c7c1b (which brought in #216, changing the transaction approval path these tests drive).
  • The suite's guarantees are untouched: ALLOWED_ERRORS is still [], TRAILING_WATCH_MS is unchanged, the interception canary is unchanged, and the unstubbed-request guard is unchanged — including the empty-batch fix that landed in the meantime.
Closes https://git.eeqj.de/sneak/AutistMask/issues/183. Test-only. `git diff origin/next --stat` touches `README.md`, `TODO.md`, `tests/e2e/network.js` and `tests/e2e/run.js` and nothing under `src/`. ## What this covers Ten new tests, taking the Chrome e2e suite from 27 to **37**. The route handler serves a local page on `https://dapp.e2e.test/`; `window.ethereum` gets there from the shipped `MAIN`-world content script, and the page's own EIP-6963 announcement is required to be that object by identity, so nothing the fixture could have installed itself satisfies it. `eth_chainId` is round-tripped through the content script and background before any prompt is driven. | DoD | Where | | --- | --- | | 1. local page from the harness, real inpage provider | `DAPP_HTML` in `tests/e2e/network.js`, test 28 | | 2. `eth_requestAccounts` / `personal_sign` / `eth_signTypedData_v4` / `eth_sendTransaction` through real prompts | tests 29-36 | | 3. every signature recovered and compared | tests 31, 33, 35 | | 4. rejection paths | tests 29, 32, 34, 36 | | 5. password never crosses the boundary | tests 31, 33, 35, 37 | | 6. demonstrated to fail | captured below | Signatures are recovered in the runner with `verifyMessage`, `verifyTypedData` and `Transaction.from()`, against the active address read out of extension storage. The background verifies too; nothing here leans on its verdict. The transaction assertions run against the raw signed bytes captured at `eth_sendRawTransaction` — the stub records them and answers with the transaction's real hash, because ethers refuses a hash mismatch — and compare signer, recipient, value, call data, chain id, and that the hash the page received is the hash of those bytes. The rejected transaction is required to have put nothing on the wire at all. The password check wraps `chrome.runtime.sendMessage` in the approval window before Approve is clicked and requires the resulting `AUTISTMASK_SIGN_RESPONSE`/`AUTISTMASK_TX_RESPONSE` record to be **present** before asserting on it, so it cannot pass by observing nothing. Test 37 re-reads both channels for the whole section and requires all four methods to appear in the page-side record first. ## Item 6 — demonstrated to fail Six runs against deliberately broken variants, all reverted. Captured output: **a. Signature recovered against the wrong address** (the recovered value corrupted in `run.js`): ``` # personal_sign: recovered=0x00000000000000000000000000000000000000Ff expected=0x55D4d5b0362F423E04c4ACE69B56c01321BCAD91 not ok 31 - personal_sign signs, and the signature recovers to the address (#183) the personal_sign signature recovers to 0x00000000000000000000000000000000000000Ff, not to the approved address 0x55D4d5b0362F423E04c4ACE69B56c01321BCAD91 not ok 33 - eth_signTypedData_v4 signs, and the signature recovers (#183) the typed data signature recovers to 0x00000000000000000000000000000000000000Ff, not to the approved address 0x55D4d5b0362F423E04c4ACE69B56c01321BCAD91 not ok 35 - eth_sendTransaction signs the approved transaction and broadcasts it (#183) the broadcast transaction was signed by 0x00000000000000000000000000000000000000Ff, not by the approved address 0x55D4d5b0362F423E04c4ACE69B56c01321BCAD91 # 33/37 tests passed # FAILED ``` **b. The password put back in the payload** — `payload.password = $("approve-sign-password").value` and the same for `approve-tx-password` in `src/popup/views/approval.js`, i.e. the shape https://git.eeqj.de/sneak/AutistMask/issues/157 removed. Caught in four places: ``` not ok 31 - personal_sign signs, and the signature recovers to the address (#183) the password crossed the extension messaging boundary on the personal_sign approval: [{"type":"AUTISTMASK_SIGN_RESPONSE","id":"854ed9ed-110c-460b-98b0-1dc11dea2331","approved":true,"password":"e2e-harness-password","signature":"0x06bea925f6be8d19b474417166d69190c96cb5b50c729e72d389125d3813b2474e5d84819ab0a2eb7d3cb7c5b069fd85dfcc0378184eabc9a12679d70ad2fee01c"}] not ok 33 - eth_signTypedData_v4 signs, and the signature recovers (#183) the password crossed the extension messaging boundary on the eth_signTypedData_v4 approval: [{"type":"AUTISTMASK_SIGN_RESPONSE","id":"4dd8e2bc-ca9d-45c5-af80-202dcd963172","approved":true,"password":"e2e-harness-password","signature":"0xbffb7caa..."}] not ok 35 - eth_sendTransaction signs the approved transaction and broadcasts it (#183) the password crossed the extension messaging boundary on the eth_sendTransaction approval: [{"type":"AUTISTMASK_TX_RESPONSE","id":"33014c7f-ee55-462f-99bb-f9128e046ef2","approved":true,"password":"e2e-harness-password","rawSignedTx":"0x02f8920180843b9aca00852ecc889a0082520894c0ffee0000000000000000000000000000c0ffee872bb2c8eabcc000a0deadbeef0101..."}] not ok 37 - the password never crossed either boundary in this section (#183) the password crossed the extension messaging boundary between the popup and the background: [ ... all three records ... ] # 32/37 tests passed # FAILED ``` **c. A provider that resolves instead of rejecting** — `p.resolve(result)` unconditionally in `src/content/inpage.js`. All four rejection paths bind: ``` not ok 29 - eth_requestAccounts rejected at the prompt returns a rejection (#183) eth_requestAccounts rejection resolved instead of rejecting: {"settled":"resolved"} not ok 32 - personal_sign rejected returns a rejection to the page (#183) personal_sign rejection resolved instead of rejecting: {"settled":"resolved"} not ok 34 - eth_signTypedData_v4 rejected returns a rejection to the page (#183) eth_signTypedData_v4 rejection resolved instead of rejecting: {"settled":"resolved"} not ok 36 - eth_sendTransaction rejected broadcasts nothing (#183) eth_sendTransaction rejection resolved instead of rejecting: {"settled":"resolved"} # 33/37 tests passed ``` **d, e, f. Approval screens displaying something other than what is signed.** Three separate runs, because the assertions shadow each other in order — a wrong origin on the sign screen, a wrong message, a wrong value and wrong call data on the transaction screen: ``` not ok 33 - eth_signTypedData_v4 signs, and the signature recovers (#183) the typed data prompt names the wrong origin: "wallet.example.com" not ok 31 - personal_sign signs, and the signature recovers to the address (#183) the sign prompt shows the wrong message: "Sign in to Example" not ok 35 - eth_sendTransaction signs the approved transaction and broadcasts it (#183) the transaction prompt shows the wrong value: "0.0000 ETH" not ok 35 - eth_sendTransaction signs the approved transaction and broadcasts it (#183) the transaction prompt does not show the approved call data: "0x" ``` Worth knowing when reading those: the suite is stateful, so a failing test leaves an approval window open and the tests after it fail on the debris. The first failure in each block is the one the mutation caused. ## What could NOT be driven, and why - **The browser-action popup itself.** `src/background/index.js` raises the site-connection prompt through `chrome.action.setPopup()` + `chrome.action.openPopup()`. Headless Chromium does open that popup, but Playwright never exposes it as a page — it is not in `ctx.pages()` after 60s, with the action URL confirmed already set. So that one prompt is driven at the URL the extension put on the action: same page, same approval id, same code path, and the route README documents for reopening a pending approval. Whether a real toolbar click puts it on screen is not observable from a headless harness and is not claimed. The sign and transaction prompts need none of this — they open real windows and are driven as-is. - **A real dApp, with real funds, against a real network.** The RPC is stubbed throughout. That remains a human pass before the 1.0.0 tag and is stated as such in `README.md` and `TODO.md`. ## Two findings, reported rather than fixed Neither is touched by this PR. 1. **The EIP-1193 error code does not reach the page.** The rejection that crosses the boundary carries `{"code":4001,"message":"User rejected the request."}` and the test asserts that on the wire, but `src/content/inpage.js` rebuilds it as `new Error(error.message)`, so the calling page catches an `Error` with no `code` property — `page Error.code=undefined page Error carries a code=false`, printed on every run. `RULES.md` requires "Rejected approvals return EIP-1193 error code 4001", and a dApp checking `err.code === 4001` sees `undefined`. The test records this rather than asserting either way, because asserting the current behaviour would make the gap permanent. 2. **Approve races the popup teardown for a site connection.** `btn-approve` calls `runtime.sendMessage()` and then `window.close()` on the next line; closing disconnects the approval port, and the disconnect handler settles a pending **site** approval as a rejection. Driven in a tab the teardown wins every time — measured: with the close in place the page is told the user rejected, with it deferred the approval goes through. The harness therefore defers `window.close()` on that one page and closes it itself once the outcome has been observed, which is documented at the call site. This does not apply to the sign or transaction prompts: their disconnect handler deliberately keeps the approval pending. Whether the same ordering holds in a real toolbar popup is not observable here. ## Verification - `make check` — green (includes `script/test-verify-build`: `test-verify-build: 18 case(s) passed`; `prettier --check .`: `All matched files use Prettier code style!`), re-run after the rebase. - `make test-e2e` — `# 37/37 tests passed`, re-run after rebasing onto `52c7c1b` (which brought in https://git.eeqj.de/sneak/AutistMask/pulls/216, changing the transaction approval path these tests drive). - The suite's guarantees are untouched: `ALLOWED_ERRORS` is still `[]`, `TRAILING_WATCH_MS` is unchanged, the interception canary is unchanged, and the unstubbed-request guard is unchanged — including the empty-batch fix that landed in the meantime.
clawbot added 1 commit 2026-08-12 13:04:25 +02:00
test: drive the EIP-1193 dApp approval round trips in the browser (closes #183)
All checks were successful
check / check (push) Successful in 33s
13d444c3aa
The seam between the content script, the inpage provider, the background worker
and the approval popup had no coverage at all. The unit suite covers each side
in isolation, so a wallet that signed the wrong payload, handed back a signature
from the wrong key, hung on a rejected prompt, or put the user's password back
on the extension messaging boundary would have passed everything.

Ten end-to-end tests now drive it. The route handler serves a local test page on
a reserved-TLD origin; window.ethereum arrives there from the shipped MAIN-world
content script, not from anything the fixture installs, and the page's own
EIP-6963 announcement is required to be that provider by object identity. The
page then speaks eth_requestAccounts, personal_sign, eth_signTypedData_v4 and
eth_sendTransaction through the real prompts, approved and rejected.

The assertions are the point:

  - every signature is recovered in the runner with verifyMessage,
    verifyTypedData and Transaction.from(), and compared against the active
    address read out of extension storage. The background verifies too; nothing
    here leans on its verdict.
  - the transaction is checked against the raw signed bytes captured at
    eth_sendRawTransaction, which the RPC stub now records and answers with the
    transaction's real hash. Signer, recipient, value, call data and chain id
    are all compared there, and the hash the page received is required to be the
    hash of those bytes.
  - a rejected prompt must reach the page as a rejection rather than hang or
    resolve, and must carry EIP-1193 code 4001 across the boundary. The code is
    asserted on the wire because that is where it survives: the inpage provider
    rebuilds the rejection as a bare Error, so the calling page catches a message
    and no code. Reported, not asserted either way.
  - the password must appear in no message the approval window sends to the
    background, observed directly by wrapping chrome.runtime.sendMessage before
    Approve is clicked, with the response message that would carry it required
    to be present so the check cannot pass on an empty record.

Every one of those was run against a deliberately broken variant and seen to
fail: a corrupted recovered signer, a payload carrying the password again, a
provider that resolves instead of rejecting, and approval screens displaying the
wrong message, value and call data.

Two honest limits. The RPC is stubbed throughout, so this does not discharge a
real dApp with real funds against a real network. And the site-connection prompt
goes through chrome.action.openPopup(), whose browser-action popup headless
Chromium will not expose as a page, so that one prompt is driven at the URL the
extension puts on the action instead — same page, same approval id, but a real
toolbar click is not observable from a headless harness. Both are stated in
README.md rather than presented as covered.

Test-only: nothing under src/ changes.
clawbot added the needs-review label 2026-08-12 13:04:30 +02:00
clawbot self-assigned this 2026-08-12 13:04:31 +02:00
Author
Collaborator

FAIL — needs-rebase. The substance holds up under adversarial probing; the branch no longer applies to next.

Finding

TODO.md:48 — conflicts with current origin/next. The new # Completed Steps bullet is inserted at the same point as the #260 bullet that landed in e4c3708. Both git rebase origin/next and git merge origin/next stop with CONFLICT (content): Merge conflict in TODO.md. Gitea still reports mergeable: true, but that is computed against the recorded base 52c7c1b, which is two commits stale. Acceptable: rebase onto current origin/next with the #183 bullet kept at the top of # Completed Steps and the #260 bullet directly beneath it, re-run make check and make test-e2e, force-push.

Nothing else. Everything below was verified on head 13d444c in an independent clone.

Probes run (mine, distinct from the six in the PR body — all reverted, tree left clean)

  • personal_sign verified against a one-word-altered message: not ok 31 … recovers to 0xE003…79ae.
  • typed data verified with the EIP-712 domain chainId changed 1 -> 5: not ok 33 … recovers to 0xf137…32bD.
  • the eth_sendRawTransaction wire record replaced with a transaction over identical fields signed by a foreign key: not ok 35 … signed by 0x19E7…ff2A. The transaction check is a real recovery from the captured bytes, not a shape check.
  • password put back in the sign payload (reproducing variant b): not ok 31 / 33 / 37.
  • password put on a message type the observer does not wait for, AUTISTMASK_TELEMETRY: not ok 35 … the password crossed the extension messaging boundary on the eth_sendTransaction approval: [{"type":"AUTISTMASK_TELEMETRY",…,"password":"e2e-harness-password"}]. The observation is not narrowed to the response messages it asserts on.

make check green and executed (637 unit tests, test-verify-build: 18 case(s) passed, prettier clean); make test-e2e # 37/37 tests passed, executed not cached; CI check / check (push) success on 13d444c.

Disclosures

  • git diff origin/next --stat -- src/ is non-empty only because the branch is behind next; against the merge base 52c7c1b it is empty. The commit changes no file under src/.
  • The popup-to-background observation wraps chrome.runtime.sendMessage only. src/popup/views/approval.js:444 opens an approval port, but nothing under src/popup/ or src/background/ ever calls port.postMessage, so it misses no live channel today — a future port-carried payload would not be seen.
  • CI runs make check only; the e2e suite these tests live in is not run by CI. Pre-existing, not introduced here.
  • The window.close() deferral is confined to reserveApprovalTab()'s own tab and documented at that call site; src/background/index.js:292 keeps tx and sign approvals pending on disconnect, so #275 is not masked for the signature or transaction paths. #274 is printed on every run (page Error.code=undefined page Error carries a code=false), asserted only on the wire — the broken behaviour is not cemented.
  • Driving the site prompt at chrome.action.getPopup()'s URL is judged legitimate: same page, same approval id, same code path, and the PR states plainly that a real toolbar click is not observable.
**FAIL — `needs-rebase`.** The substance holds up under adversarial probing; the branch no longer applies to `next`. ## Finding **`TODO.md:48` — conflicts with current `origin/next`.** The new `# Completed Steps` bullet is inserted at the same point as the `#260` bullet that landed in `e4c3708`. Both `git rebase origin/next` and `git merge origin/next` stop with `CONFLICT (content): Merge conflict in TODO.md`. Gitea still reports `mergeable: true`, but that is computed against the recorded base `52c7c1b`, which is two commits stale. Acceptable: rebase onto current `origin/next` with the [#183](https://git.eeqj.de/sneak/AutistMask/issues/183) bullet kept at the top of `# Completed Steps` and the [#260](https://git.eeqj.de/sneak/AutistMask/issues/260) bullet directly beneath it, re-run `make check` and `make test-e2e`, force-push. Nothing else. Everything below was verified on head `13d444c` in an independent clone. ## Probes run (mine, distinct from the six in the PR body — all reverted, tree left clean) - `personal_sign` verified against a one-word-altered message: `not ok 31 … recovers to 0xE003…79ae`. - typed data verified with the EIP-712 domain `chainId` changed 1 -> 5: `not ok 33 … recovers to 0xf137…32bD`. - the `eth_sendRawTransaction` wire record replaced with a transaction over identical fields signed by a foreign key: `not ok 35 … signed by 0x19E7…ff2A`. The transaction check is a real recovery from the captured bytes, not a shape check. - password put back in the sign payload (reproducing variant b): `not ok 31 / 33 / 37`. - password put on a message type the observer does not wait for, `AUTISTMASK_TELEMETRY`: `not ok 35 … the password crossed the extension messaging boundary on the eth_sendTransaction approval: [{"type":"AUTISTMASK_TELEMETRY",…,"password":"e2e-harness-password"}]`. The observation is not narrowed to the response messages it asserts on. `make check` green and executed (637 unit tests, `test-verify-build: 18 case(s) passed`, prettier clean); `make test-e2e` `# 37/37 tests passed`, executed not cached; CI `check / check (push)` success on `13d444c`. ## Disclosures - `git diff origin/next --stat -- src/` is non-empty only because the branch is behind `next`; against the merge base `52c7c1b` it is empty. The commit changes no file under `src/`. - The popup-to-background observation wraps `chrome.runtime.sendMessage` only. `src/popup/views/approval.js:444` opens an approval port, but nothing under `src/popup/` or `src/background/` ever calls `port.postMessage`, so it misses no live channel today — a future port-carried payload would not be seen. - CI runs `make check` only; the e2e suite these tests live in is not run by CI. Pre-existing, not introduced here. - The `window.close()` deferral is confined to `reserveApprovalTab()`'s own tab and documented at that call site; `src/background/index.js:292` keeps `tx` and `sign` approvals pending on disconnect, so [#275](https://git.eeqj.de/sneak/AutistMask/issues/275) is not masked for the signature or transaction paths. [#274](https://git.eeqj.de/sneak/AutistMask/issues/274) is printed on every run (`page Error.code=undefined page Error carries a code=false`), asserted only on the wire — the broken behaviour is not cemented. - Driving the site prompt at `chrome.action.getPopup()`'s URL is judged legitimate: same page, same approval id, same code path, and the PR states plainly that a real toolbar click is not observable.
clawbot added needs-rebase and removed needs-review labels 2026-08-12 13:17:05 +02:00
clawbot force-pushed test/issue-183-dapp-round-trips from 13d444c3aa to dbd83fee02 2026-08-12 13:23:18 +02:00 Compare
clawbot merged commit d5595c0151 into next 2026-08-12 13:23:58 +02:00
clawbot deleted branch test/issue-183-dapp-round-trips 2026-08-12 13:23:58 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/AutistMask#273