Files
AutistMask/eslint.config.js
sneak 031a70e0b6
All checks were successful
check / check (push) Successful in 53s
e2e / e2e-chrome (push) Successful in 1m6s
e2e / e2e-firefox (push) Successful in 41s
feat: vendor and censor the phishing blocklist at build time (closes #219)
The blocklist URL in shipped code named a competitor and pointed at a moving
ref, and the extension re-fetched from it every 24 hours, which also meant a
third party decided what this wallet warns about. All of that is gone.

script/vendor-blocklist fetches upstream at a pinned commit, verifies the
sha256 of the bytes that commit serves, and writes
src/shared/phishingBlocklist.json. It is build-time tooling, never shipped, and
the one place in the repo that names the upstream project; a source reference
nobody can verify is not a source reference.

The artifact stores truncated sha256 digests rather than domain names. That is
what censors it: the previous file contained the competitor's name 6,475 times,
as phishing domains impersonating them, and not one of those domains is
dropped. It also makes lookups a binary search over a fixed-width string, so
nothing is built at module load — which matters on MV3, where the worker
re-evaluates the module on every wake — and takes the file from 8.7 MB to
1.7 MB.

script/check-censored enforces the rest: it reads the name out of the vendoring
script rather than repeating it, and fails on any occurrence in the working
tree or under dist/ that is not one of the three literals shipped code cannot
avoid — two provider-shim identifiers in src/content/inpage.js and one ERC-20's
on-chain name in src/shared/tokenList.js. Each is permitted only at the path
that carries it, and at the emitted paths that path is bundled into, so a
literal appearing anywhere else fails like any other occurrence. It runs in
make check, which inspects dist/ when there is one and says loudly when there
is not, and again with --require-dist at the end of every make build.

Removing the runtime fetch retires the delta, the extension-storage persistence
and the 24-hour alarm from #158. A retired alarm is now cleared rather than
left waking the worker forever on installs that already have it.

The e2e suite drives the warning end to end from a real blocklisted origin
served as a real http(s) site, with a control asserting the banner stays hidden
for one that is not listed. Its service-worker interception canary needed a new
anchor, since the startup fetch it used to watch for no longer happens: it now
wakes the worker with a message and asks it for one throwaway fetch.

LICENSE no longer cites a repository that returns 404.

eslint.config.js gains one block: script/lib/ holds node programs the shell
entrypoints call, and without it they lint with no globals at all.
2026-08-17 07:48:47 +00:00

163 lines
5.5 KiB
JavaScript

// ESLint flat config. Static analysis for make check; formatting stays with
// prettier (script/fmt-check), so nothing here touches style.
//
// The sources are CommonJS and are bundled per entrypoint by build.js, so the
// globals differ by tree and are declared per tree below. Getting that wrong in
// either direction defeats the point: too few globals buries a real no-undef in
// false positives, too many hides the next unimported identifier.
const js = require("@eslint/js");
const globals = require("globals");
// The extension APIs. MV3 Chrome exposes `chrome`; Firefox exposes both, and
// the code feature-detects between them.
const extensionGlobals = {
chrome: "readonly",
browser: "readonly",
};
const commonjs = {
ecmaVersion: 2024,
sourceType: "commonjs",
};
module.exports = [
{
ignores: ["dist/", "node_modules/"],
},
js.configs.recommended,
{
rules: {
// The two rules this config exists for. Both are already
// error-level in the recommended set; restated so a future
// recommended-set change cannot silently downgrade them.
"no-undef": "error",
// `_`-prefixed arguments are the deliberate "present for the
// interface, unused here" marker: the popup views share one
// init(ctx) signature and three of the eight do not read ctx.
// An unused catch binding is written `catch {`, which the repo
// already does, so caught errors stay checked.
"no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
// Off tree-wide: it requires every rethrow to carry `{ cause }`,
// at 3 sites today (src/shared/balances.js 207 and 215,
// tests/e2e/firefox/run.js 131). That is a change to what the
// wallet's error paths actually throw, and it is a decision of its
// own rather than a side effect of turning a linter on — so it is
// off everywhere, including for new code, until that decision is
// made. Unlike no-useless-assignment below, this is not an
// accommodation of particular sites and must not be scoped to
// them.
"preserve-caught-error": "off",
},
},
// no-useless-assignment stays on everywhere except the two files that
// wipe decrypted key material: the `password = null` and
// `decryptedSecret = null` assignments after use are dead by construction
// — that is what a best-effort wipe is — and the rule's fix is to delete
// the wipe. 9 sites: approval.js 582, 593, 618, 648, 692, 703, 728, 764
// and confirmTx.js 459. Everything else in the tree is still checked, so
// an ordinary dead store elsewhere is still an error.
{
files: ["src/popup/views/approval.js", "src/popup/views/confirmTx.js"],
rules: {
"no-useless-assignment": "off",
},
},
// Popup and content scripts: page/window context.
{
files: ["src/popup/**/*.js", "src/content/**/*.js"],
languageOptions: {
...commonjs,
globals: { ...globals.browser, ...extensionGlobals },
},
},
// MV3 background: a service worker, with no window and no document.
{
files: ["src/background/**/*.js"],
languageOptions: {
...commonjs,
globals: { ...globals.serviceworker, ...extensionGlobals },
},
},
// src/shared is bundled into both, so it may only use what both provide:
// the service worker globals are the intersection, plus the extension APIs.
{
files: ["src/shared/**/*.js"],
languageOptions: {
...commonjs,
globals: { ...globals.serviceworker, ...extensionGlobals },
},
},
// src/shared/ens.js is the documented exception to the line above: its own
// header says POPUP ONLY, it caches in localStorage, and only popup views
// require it. Linting it as a service worker would be wrong about the file.
{
files: ["src/shared/ens.js"],
languageOptions: {
...commonjs,
globals: { ...globals.browser, ...extensionGlobals },
},
},
// Unit tests: jest on node.
{
files: ["tests/**/*.test.js"],
languageOptions: {
...commonjs,
globals: { ...globals.node, ...globals.jest },
},
},
// The build script is a plain node program.
{
files: ["build.js"],
languageOptions: {
...commonjs,
globals: { ...globals.node },
},
},
// The helpers the script/ entrypoints call: plain node programs too, run
// from a shell script rather than from yarn, and never bundled.
{
files: ["script/lib/**/*.js"],
languageOptions: {
...commonjs,
globals: { ...globals.node },
},
},
// The e2e harnesses are node programs that also carry, inline, the
// callbacks they ship into the browser via page.evaluate — so both
// contexts really are present in the same file and both sets of globals
// are in scope somewhere in it.
{
files: ["tests/e2e/**/*.js"],
languageOptions: {
...commonjs,
globals: {
...globals.node,
...globals.browser,
...extensionGlobals,
},
},
},
// This config file itself.
{
files: ["eslint.config.js"],
languageOptions: {
...commonjs,
globals: { ...globals.node },
},
},
];