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.
This commit is contained in:
105
script/vendor-blocklist
Executable file
105
script/vendor-blocklist
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/bin/sh
|
||||
# script/vendor-blocklist: refresh the vendored phishing blocklist at
|
||||
# src/shared/phishingBlocklist.json from its upstream source. Our own extension
|
||||
# to scripts-to-rule-them-all.
|
||||
#
|
||||
# This is build-time repo tooling and is not shipped. It is the one place in
|
||||
# this repo that names the upstream project, because a source reference that
|
||||
# does not say what the source is cannot be verified by anyone; the artifact it
|
||||
# writes carries no names at all (see src/shared/domainHash.js).
|
||||
# script/check-censored reads the name back out of this file rather than
|
||||
# repeating it, so it stays defined exactly once.
|
||||
#
|
||||
# Run it deliberately, not on every build: the output is committed, and the
|
||||
# extension does no runtime fetching, so the shipped list is exactly as fresh as
|
||||
# the last time someone ran this and landed the result. Re-run it, land the
|
||||
# diff, cut a release; that is the whole refresh path.
|
||||
#
|
||||
# Pinned by content hash, twice over, as REPO_POLICIES.md requires. The commit
|
||||
# below is an immutable ref — the upstream default branch moves several times a
|
||||
# day and cannot be pinned — and UPSTREAM_SHA256 is the sha256 of the bytes that
|
||||
# commit serves. A mismatch is a hard failure: a vendoring step that accepts
|
||||
# whatever it is handed is a supply-chain hole, and this one feeds a security
|
||||
# warning shown to users.
|
||||
#
|
||||
# To move the pin: pick the new commit, run this with the new UPSTREAM_COMMIT
|
||||
# and an UPSTREAM_SHA256 you have not yet updated, and it will print the hash it
|
||||
# actually got. Verify that hash against the source independently before
|
||||
# recording it. Never copy the "actual" line in on trust.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
# Upstream, pinned 2026-08-17.
|
||||
UPSTREAM_ORG="MetaMask"
|
||||
UPSTREAM_REPO="eth-phishing-detect"
|
||||
UPSTREAM_COMMIT="6dddf74a87da3e1a0841f7ae0d1cb31aaf2c05db"
|
||||
UPSTREAM_FILE="src/config.json"
|
||||
UPSTREAM_SHA256="166d5b3504e8f4ed52eae37d3dd20c1a56efa0502bfb3dc957044ff8b5f1283f"
|
||||
|
||||
OUTPUT="src/shared/phishingBlocklist.json"
|
||||
|
||||
WORK=""
|
||||
|
||||
cleanup() {
|
||||
[ -z "$WORK" ] || rm -rf "$WORK"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
fail() {
|
||||
echo "vendor-blocklist: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
sha256_of() {
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256sum "$1" | cut -d' ' -f1
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
shasum -a 256 "$1" | cut -d' ' -f1
|
||||
else
|
||||
fail "neither sha256sum nor shasum is available, so the fetched
|
||||
source cannot be verified. Refusing to vendor unverified content."
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
|
||||
command -v curl >/dev/null 2>&1 ||
|
||||
fail "curl is required to fetch the upstream list"
|
||||
command -v node >/dev/null 2>&1 ||
|
||||
fail "node is required to build the artifact; run script/bootstrap"
|
||||
|
||||
WORK="$(mktemp -d "${TMPDIR:-/tmp}/autistmask-vendor-blocklist.XXXXXX")" ||
|
||||
fail "could not create a working directory"
|
||||
|
||||
url="https://raw.githubusercontent.com/$UPSTREAM_ORG/$UPSTREAM_REPO/$UPSTREAM_COMMIT/$UPSTREAM_FILE"
|
||||
echo "Fetching $url"
|
||||
curl -fsSL --proto '=https' --tlsv1.2 -o "$WORK/source.json" "$url" ||
|
||||
fail "the fetch failed, so nothing was vendored"
|
||||
|
||||
actual="$(sha256_of "$WORK/source.json")"
|
||||
if [ "$actual" != "$UPSTREAM_SHA256" ]; then
|
||||
fail "sha256 mismatch on the fetched source.
|
||||
expected: $UPSTREAM_SHA256
|
||||
actual: $actual
|
||||
The pinned commit is immutable, so the same commit serving different bytes
|
||||
means the content was substituted somewhere between upstream and here.
|
||||
Nothing was written. Do not update the expectation to match unless you have
|
||||
verified the new bytes independently."
|
||||
fi
|
||||
echo "Verified sha256 $actual"
|
||||
|
||||
node script/lib/build-blocklist.js "$WORK/source.json" "$WORK/out.json" ||
|
||||
fail "the transform failed, so nothing was written"
|
||||
|
||||
if [ -f "$OUTPUT" ] && cmp -s "$WORK/out.json" "$OUTPUT"; then
|
||||
echo "vendor-blocklist: $OUTPUT is already up to date"
|
||||
return 0
|
||||
fi
|
||||
|
||||
cp "$WORK/out.json" "$OUTPUT"
|
||||
echo "vendor-blocklist: wrote $OUTPUT (sha256 $(sha256_of "$OUTPUT"))"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user