Files
AutistMask/TODO.md
clawbot 52fb765232
All checks were successful
check / check (push) Successful in 35s
test: containerized Firefox end-to-end harness (closes #184)
Drives the real popup in a real Firefox with dist/firefox/ installed as an
unpacked MV2 temporary add-on, via geckodriver. Covers popup load, wallet
creation through the UI, and the Add Token screen. Outside make check, like
the Chrome suite.

Zero npm dependencies: tests/e2e/firefox/driver.js is a WebDriver client
over global fetch and child_process against geckodriver's HTTP API. The
Dockerfile pins the node base image, the Firefox 153.0.3 tarball and
geckodriver 0.36.0 by digest.

Errors are read from the privileged nsIConsoleService in Marionette's chrome
context, filtered to non-warning entries whose sourceName is the extension
origin. BiDi log.entryAdded delivers nothing at all for extension pages, so
a Playwright-BiDi or Puppeteer-BiDi harness would see nothing and report
success; the code says so where someone would be tempted to simplify it.
Errors logged during add-on install and background startup are drained and
folded into step 1, never discarded: a throw at the top of
src/background/index.js kills the background page and fails the run.
Content-script capture is left as unverified, because --network none leaves
no http:// page for a content script to be injected into.

Each drain reads the console and clears it in ONE chrome script. Splitting
the read from Services.console.reset() left a window between the two round
trips in which an error was logged into a buffer about to be discarded, and
destroyed unread rather than deferred to the next drain; a probe of 100
sequenced throws at 20ms spacing lost one. With the drain atomic the same
probe accounts for every throw that falls inside the observed window, on two
consecutive runs.

No driver layer is shared with the Chrome suite and the three UI steps are
written twice deliberately: the two backends have no common substrate, and
three steps do not pay for a shim.

Two limits are documented rather than papered over, with measurements rather
than absolutes. Error capture is poll-based, so an error is attributed to a
step and not to a moment within it; the drained window ends ~1.5s after the
last step returns (a 500ms settle, a 1000ms sleep and two drain round trips),
and that cut-off jitters run to run — three runs of throws at fixed offsets
reported everything up to +1.5s and one of the three also reported +1.6s.
Inside the window the atomic drain leaves no race, but nsIConsoleService
keeps a ring buffer of only 250 messages, so more than 250 console messages
between two drains evicts unread errors: 400 throws inside one step report
as exactly the newest 250, on three runs, while occupancy in a clean run
peaks at 4 of 250 at the install drain and 0 at every later drain. Nothing is
stubbed; the container runs with --network none instead, which proves no
request escaped, cannot report which were attempted, and runs only the
failure branches of network-dependent code.
2026-08-12 09:56:55 +00:00

280 lines
17 KiB
Markdown

# Workflow
- `git pull` `next` and cut a branch from it — one branch per issue, named
`issue-<N>-<slug>`. Never branch from `main`.
- Do the work as one commit whose title ends with ` (closes #N)`, with the
`TODO.md` update in that same commit.
- Move Next Step to the top of Completed Steps; move the top item of Future
Steps into Next Step.
- Run `make fmt`, then `make check`. A feature branch may be red; `next` and
`main` may not.
- Rebase onto current `next` immediately before pushing — other branches land on
`next` continuously — and re-run `make check` after resolving, because a clean
textual merge can still break the build.
- Push the branch and open one PR per issue with base `next`. Never base `main`.
- An independent reviewer who did not write the change gates the merge. On a
passed review the PR is squash-merged into `next`.
- `next` is the branch for the next milestone. It is kept green and mergeable to
`main` at any moment, without notice.
- `main` receives exactly one PR per milestone, from `next`. Releases are tagged
from `main`.
# Status
pre-1.0, working towards the 1.0.0 milestone. Tagged v0.1.0 on 2026-02-27. The
milestone is in flight on `next`; its `next` -> `main` PR is
[#190](https://git.eeqj.de/sneak/AutistMask/pulls/190). `make check` verified
green on `next` at `e9fa8be` on 2026-08-10, and `make build` produces
`dist/chrome/` and `dist/firefox/` with every bundle verified to have `DEBUG`
compiled off.
The backlog lives on the
[Gitea tracker](https://git.eeqj.de/sneak/AutistMask/issues), which is
authoritative; this file does not duplicate it. Full policy file set present.
Real-browser end-to-end suites (`make test-e2e` for Chrome,
`make test-e2e-firefox` for Firefox) now sit alongside `make check`, which
cannot see a runtime `ReferenceError` in a popup view.
# Next Step
Land [#152](https://git.eeqj.de/sneak/AutistMask/issues/152): add ESLint to
`script/lint`. `make check` is `prettier --check` only today and cannot catch
undefined identifiers, which is how
[#150](https://git.eeqj.de/sneak/AutistMask/issues/150) and
[#151](https://git.eeqj.de/sneak/AutistMask/issues/151) shipped.
# Completed Steps
- 2026-08-12: A containerized Firefox end-to-end harness
(`make test-e2e-firefox`) drives the real popup in a real Firefox with the MV2
build installed as a temporary add-on. Zero npm dependencies — a WebDriver
client over `fetch` against geckodriver — with `node`, Firefox 153.0.3 and
geckodriver 0.36.0 all pinned by digest. Uncaught errors are read from the
privileged console service in Marionette's chrome context, because BiDi
`log.entryAdded` reports nothing at all for extension pages; each drain reads
and clears the console in one chrome round trip, so no error is destroyed
unread by the drain itself, and errors logged during add-on install and
background startup are folded into step 1 instead of being cleared. The two
measured limits are documented rather than claimed away: the console ring
buffer holds 250 messages (a clean run peaks at 4), and the drained window
ends ≈1.5s after the last step returns. Demonstrated discriminating by exiting
1 on a `throw` at the top of `src/background/index.js`, on a build with one
import removed, on a `setTimeout` throw whose UI assertions all pass, on an
unhandled `Promise.reject` and on an undefined identifier in `home.js`, and 0
on the branch as it stands
([#184](https://git.eeqj.de/sneak/AutistMask/issues/184)).
- 2026-08-12: Closed the empty-array hole in the end-to-end unstubbed-request
guard. `batch.every()` is vacuously true on `[]`, so a POST with body `[]` was
answered `200 []` instead of failing the suite; the guard now rejects an empty
batch, demonstrated green-before/red-after with a throwaway probe. The comment
claiming `postData()` returns `null` for undecodable bodies was corrected to
the two real paths — an absent or empty body decodes to `null`, a binary body
decodes lossily into invalid JSON
([#187](https://git.eeqj.de/sneak/AutistMask/issues/187)).
- 2026-08-12: The transaction confirmation screen has browser coverage. The
end-to-end suite reaches ConfirmTx for both the native ETH and the ERC-20 path
off a funded-balance fixture, and asserts the pending, funded, over-balance
and estimate-failed states, the fee block quoting the estimate and the reserve
separately, and a constant view height across every one of those transitions.
The load-bearing assertion is that the spend gate reads the reserve and not
the displayed estimate: swapping the two fails the suite
([#238](https://git.eeqj.de/sneak/AutistMask/issues/238)).
- 2026-08-12: The dust threshold field now explains a rejection instead of
snapping back in silence, with the parse in a pure, unit-tested module that
accepts plain decimal digits only — hex and exponent notation are refused
rather than read as 16 and 1000
([#233](https://git.eeqj.de/sneak/AutistMask/issues/233)).
- 2026-08-12: Approval verification became an allowlist — transaction type
restricted to 0/1/2 so an EIP-7702 delegation can no longer ride along on an
approved transfer, every consequential field compared, the artifact
re-serialized from the checked fields alone and its exact bytes required to be
the canonical encoding of what was broadcast. One approval now yields at most
one broadcast, and every path that retires a pending approval — popup close,
active-address change, a late reject — goes through a single chokepoint that
refuses to settle an attempt already claimed for signing and broadcast
([#174](https://git.eeqj.de/sneak/AutistMask/issues/174)).
- 2026-08-12: An address can be removed from an HD or xprv wallet behind a
confirmation screen that states nothing is destroyed, sharing the deletion
state transitions with wallet deletion so the selection, site permissions and
active-address broadcast follow the same rules
([#162](https://git.eeqj.de/sneak/AutistMask/issues/162)).
- 2026-08-12: The known-symbol spoof rule moved into `src/shared/symbolSpoof.js`
and is now the only copy. The balance list had exempted symbols the token list
maps to `null``"ETH"` alone — so a fake ETH ERC-20 was hidden from the
transaction history and the Send selector but listed as a holding named ETH. A
symbol with no legitimate contract may now be borne by no contract on any of
the three surfaces, and the native exemption is "has no contract address", so
a second null-mapped symbol needs no call-site change. The user's real ETH
balance is read over RPC and never passes through the rule
([#235](https://git.eeqj.de/sneak/AutistMask/issues/235)).
- 2026-08-12: `script/verify-build`'s failure modes are now a committed target,
`script/test-verify-build`, run by `make check`. It asserts the exit status
and the message of every case against a fixture tree in a temp dir, and drops
privileges (proving the runner against a mode-000 file first) for the cases
that only mean something when file permissions are in force
([#227](https://git.eeqj.de/sneak/AutistMask/issues/227)).
- 2026-08-12: WaitTx lifecycle: a receipt and the 60-second timeout can no
longer both render on one tick, no timer or in-flight lookup outlives its
wait, a failed receipt lookup no longer counts as a timeout (but six in a row
end the wait, reported as an unreachable network rather than as a timeout),
and the wait now resumes after a popup close
([#155](https://git.eeqj.de/sneak/AutistMask/issues/155)).
- 2026-08-12: The private key export screen now wipes the key from the page
whenever it is left by any route, and a decrypt still in flight when the
screen is left is discarded instead of written; the same `onViewLeave()`
cleanup was extended to every other screen holding secret material in the DOM
(AddWallet, ConfirmTx, DeleteWallet, ApproveTx, ApproveSign)
([#221](https://git.eeqj.de/sneak/AutistMask/issues/221)).
- 2026-08-12: An xprv wallet already in storage that was imported from a
non-master key is detected from the depth of its stored `xpub`, explained in
the wallet list, and blocked from signing, sending and private-key export
instead of throwing on the send screen
([#234](https://git.eeqj.de/sneak/AutistMask/issues/234)).
- 2026-08-12: An unreported `holders_count` is now parsed as `null` rather than
`0`, so the low-holder rule declines to judge an unknown count instead of
hiding a legitimate token as spam, in both the transaction history and the
Send token selector ([#230](https://git.eeqj.de/sneak/AutistMask/issues/230)).
- 2026-08-12: Bundled token list documentation no longer states a count. The
four "top 250" claims in `README.md` and the "roughly 500" claim in
`docs/README.md` are replaced with a description of how the list is actually
selected — a point-in-time CoinGecko snapshot of the highest-market-cap
Ethereum mainnet ERC-20s — with `TOKENS` in `src/shared/tokenList.js` named as
the authoritative set
([#239](https://git.eeqj.de/sneak/AutistMask/issues/239)).
- 2026-08-11: libsodium runs on WebAssembly in the shipped builds —
`'wasm-unsafe-eval'` added to both manifest CSPs after measuring the wasm2js
fallback at 20x the Argon2id cost, pinned in both directions by
`tests/manifest.test.js` and observed in the real popup by the e2e suite
([#182](https://git.eeqj.de/sneak/AutistMask/issues/182)).
- 2026-08-11: Known-symbol spoof verification became a Settings toggle
(`hideSpoofedSymbols`), on by default, governing the transaction-history
filter and the fraud-contract learning it feeds
([#176](https://git.eeqj.de/sneak/AutistMask/issues/176)).
- 2026-08-11: `script/verify-build` now walks `dist/` NUL-delimited and asserts
`dist/` is a real directory, so a path with a trailing space or a newline can
no longer carry a debug marker past the unlisted-bundle check
([#223](https://git.eeqj.de/sneak/AutistMask/issues/223)).
- 2026-08-11: UTC Timestamps checkbox moved from the Token Spam Protection well
into Display, next to the theme selector
([#212](https://git.eeqj.de/sneak/AutistMask/issues/212)).
- 2026-08-11: Network fee counted in the confirmation-screen balance check for
both ETH and ERC-20 sends, reserving what the node actually charges a type-2
transaction, with the arithmetic in a pure, unit-tested
`src/shared/txValidation.js`
([#154](https://git.eeqj.de/sneak/AutistMask/issues/154)).
- 2026-08-11: A dust threshold of `0` now means "hide nothing" instead of
falling back to the 100,000 gwei default, and every address comparison in
`src/shared/transactions.js` goes through one case-normalising helper so a
checksummed genuine contract is no longer read as a spoof
([#179](https://git.eeqj.de/sneak/AutistMask/issues/179)).
- 2026-08-11: Password-gated recovery phrase display for HD wallets, reached
from the wallet row in Settings, wiped on leaving the screen and excluded from
the views the popup can reopen onto
([#161](https://git.eeqj.de/sneak/AutistMask/issues/161)).
- 2026-08-11: Extended-key import hardened — the base58 checksum is now enforced
on every xprv and xpub, and a non-master key is refused with an explanation
instead of being derived beneath
([#210](https://git.eeqj.de/sneak/AutistMask/issues/210)).
- 2026-08-11: the balance refresh and the 24-hour phishing list refresh moved
from `setInterval` to the extension alarms API, with the phishing delta and
its fetch timestamps persisted to extension storage, so neither job dies with
the MV3 service worker. Each job's freshness guard was decoupled from its
alarm period at the same time — timed to the period, a guard vetoes its own
scheduled tick and halves the real refresh rate
([#158](https://git.eeqj.de/sneak/AutistMask/issues/158)).
- 2026-08-11: Policy compliance sweep — conditional verbose test rerun, local
Tailwind binary instead of `npx`, `--frozen-lockfile` on `make install`, and
the Makefile-only targets documented in the README
([#166](https://git.eeqj.de/sneak/AutistMask/issues/166)).
- 2026-08-11: `script/verify-build` diagnostics corrected: the both-markers
message now states what is and is not proven, an unreadable bundle is
diagnosed as an I/O fault rather than as changed output, the `*.js` assumption
lives only in `build.js`, and the unlisted-bundle scan hard-fails when it
cannot enumerate `dist/`
([#180](https://git.eeqj.de/sneak/AutistMask/issues/180)).
- 2026-08-11: Known-answer test coverage for the crypto core — BIP-39/BIP-32
derivation in `wallet.js` and the Argon2id vault in `vault.js`
([#159](https://git.eeqj.de/sneak/AutistMask/issues/159)).
- 2026-08-11: Three `README.md` claims corrected against the code — blocklist
attribution, token-display rule, navigation model
([#213](https://git.eeqj.de/sneak/AutistMask/issues/213)).
- 2026-08-11: README Screen Map rebuilt from the code — every screen, element
and transition re-verified against `src/popup/`
([#164](https://git.eeqj.de/sneak/AutistMask/issues/164)).
- 2026-08-11: `docs/README.md` rewritten against the code: no competitor names,
all five network destinations documented, password/Settings/Add Wallet
sections corrected ([#163](https://git.eeqj.de/sneak/AutistMask/issues/163)).
- 2026-08-11: `loadState()` now derives `hasWallet` from the wallet list instead
of trusting the persisted flag, so a profile already saved inconsistent no
longer stays broken on every load
([#195](https://git.eeqj.de/sneak/AutistMask/issues/195)).
- 2026-08-11: Wallet deletion repairs its own state — `hasWallet` follows the
remaining wallets, the selection only moves when it was deleted, and the
active-address change is broadcast to connected sites
([#156](https://git.eeqj.de/sneak/AutistMask/issues/156)).
- 2026-08-11: One row per on-chain value movement in transaction history: the
merge moved into the pure `mergeTransactions` and the zero-ETH native side of
a plain ERC-20 transfer absorbed into its token row
([#177](https://git.eeqj.de/sneak/AutistMask/issues/177)).
- 2026-08-11: `TODO.md` Workflow rewritten to the branch-and-PR-per-issue model
on `next`, with Status and Next Step refreshed
([#191](https://git.eeqj.de/sneak/AutistMask/issues/191)).
- 2026-08-09: `DEBUG` became a build-time constant defaulting to off, injected
as the `__BUILD_DEBUG__` esbuild define and turned on with
`AUTISTMASK_DEBUG=1`, so a plain `make build` no longer hands every newly
created wallet the publicly committed test recovery phrase
([#149](https://git.eeqj.de/sneak/AutistMask/issues/149)).
- 2026-08-09: dApp approval signing moved into the popup — the password no
longer crosses the extension messaging boundary; the background broadcasts and
resolves approvals only, and verifies the signed artifact against the approval
it holds (#157).
- 2026-08-09: Post-build assertion that every emitted bundle containing
`constants.js` has `DEBUG` compiled off, via `script/verify-build` on the
`make build` path (#170).
- 2026-08-09: Containerized Chrome end-to-end harness (`make test-e2e` /
`script/test-e2e`) driving the real popup with all network intercepted, plus
the two used-but-not-imported crashes it caught: AddToken unreachable (#150)
and TransactionDetail broken for every ERC-20 transfer (#151). Harness
demonstrated failing before the fixes and passing after (#181). Interception
covers the MV3 background service worker, not just the popup page, and a
launch-time canary aborts the suite if worker traffic starts escaping.
- 2026-08-09: Reviewed the repo end to end and filed the 1.0.0 backlog
(#149-#168).
- 2026-08-09: Test coverage for the address-poisoning defense in
`src/shared/transactions.js` (#160)
- 2026-07-26: About well in settings with build info, repo link and the version
click easter egg (#145); proper view navigation stack (#146).
- 2026-07-07 Adopted scripts-to-rule-them-all: `script/` entrypoints, Makefile
shims, README Entrypoints section (#148)
- 2026-03-01: USD display suppressed on testnets (#142); estimated USD for ETH
in approve-tx view (#141).
- Sepolia testnet support (#137); etherscan links go to token-specific URLs
(#136).
- Transaction detail improvements: Type field and on-chain details (#130),
txid-first reordering (#133), swap display corrections (#128), expanded
confirm-tx warnings (#118).
- Dark mode theme setting (Light/Dark/System) with contrast fixes (#126);
timestamps include timezone offset (#120); layout shift audit, reserved space
for error messages (#124).
- Copy-flash visual feedback with timing tune (#113, #121); cross-wallet-type
duplicate detection (#115).
- 2026-02-27: v0.1.0 tagged.
- 2026-02-24: Initial scaffolding: popup UI, BIP-39 wallet creation via
ethers.js, wallet persistence, real ETH balances over RPC, ENS forward and
reverse resolution.
# Future Steps
Only work that has no issue of its own belongs here; everything else is on the
tracker.
- Pre-1.0 security review of the extension (key handling, DEBUG mode policy, RPC
input validation) before any 1.0rc tag. Individual filed issues are parts of
it, but the review is broader than any of them.
- Decide whether docker-in-docker makes `make test-e2e` and
`make test-e2e-firefox` runnable in the Gitea workflow. Extending the Chrome
suite itself is tracked as
[#183](https://git.eeqj.de/sneak/AutistMask/issues/183).
- Cut 1.0.0 once the milestone is empty, then continue tagging as milestones
land.