docs: state the enforced dist/ verification scope precisely (closes #331)
All checks were successful
check / check (push) Successful in 29s
e2e / e2e-chrome (push) Successful in 1m11s
e2e / e2e-firefox (push) Successful in 22s

README, the script synopsis, its header paragraph and the check_dist_tree comment now all say the same thing: regular files and symlinks under dist/ are covered; fifos, sockets, device nodes and empty directories are not, and why. No behaviour change — the walk is untouched.
This commit was merged in pull request #338.
This commit is contained in:
2026-08-23 15:33:58 +02:00
parent cef6aaab11
commit c36d8b6ddf
3 changed files with 51 additions and 27 deletions

View File

@@ -81,17 +81,21 @@ lives.
one of the bundles containing `src/shared/constants.js` — into a build receipt, one of the bundles containing `src/shared/constants.js` — into a build receipt,
and `script/verify-build` checks `dist/` against that receipt: every recorded and `script/verify-build` checks `dist/` against that receipt: every recorded
file present with exactly the recorded bytes, every audited bundle carrying the file present with exactly the recorded bytes, every audited bundle carrying the
requested `DEBUG` marker, and nothing under `dist/` that the build did not requested `DEBUG` marker, and no regular file or symlink under `dist/` that the
write. The `Makefile` creates the receipt path with `mktemp` per invocation, build did not write. The `Makefile` creates the receipt path with `mktemp` per
outside the repo, and deletes it afterwards. invocation, outside the repo, and deletes it afterwards.
That is what ties the check to a build rather than to a directory. What it That is what ties the check to a build rather than to a directory. What it
establishes is narrow and worth stating exactly: `dist/` is byte for byte the establishes is narrow and worth stating exactly: `dist/` is byte for byte the
output of the `build.js` run that just finished, with nothing added, removed or output of the `build.js` run that just finished, with no regular file or symlink
altered in between. It establishes nothing about whether the source tree or added, removed or altered in between. Regular files and symlinks are the whole
`build.js` were honest, and it offers nothing to someone handed a `dist/` from of what the tree walk covers; fifos, sockets, device nodes and empty directories
elsewhere — without the receipt from its own build there is no input to the under `dist/` are not checked, because a build emits none of them, none can
check. Verifiable provenance for a third party is signing, which this is not. carry a shippable payload, and `grep` on a fifo would hang rather than fail. It
establishes nothing about whether the source tree or `build.js` were honest, and
it offers nothing to someone handed a `dist/` from elsewhere — without the
receipt from its own build there is no input to the check. Verifiable provenance
for a third party is signing, which this is not.
There is deliberately no target that re-verifies an existing `dist/` on its own. There is deliberately no target that re-verifies an existing `dist/` on its own.
The list of files to check has to come from the build that produced them; read The list of files to check has to come from the build that produced them; read
@@ -143,12 +147,13 @@ provide:
serves. Run deliberately, never as part of a build: the output is committed serves. Run deliberately, never as part of a build: the output is committed
and there is no runtime fetch, so the shipped list is as fresh as the last and there is no runtime fetch, so the shipped list is as fresh as the last
vendoring run that was released vendoring run that was released
- `script/verify-build --expect release|debug --receipt PATH` — assert that - `script/verify-build --expect release|debug --receipt PATH` — assert that the
`dist/` is exactly what the build that just ran emitted, and that the compiled regular files and symlinks under `dist/` are exactly what the build that just
`DEBUG` state of the bundles in it is the one that was asked for. Both ran emitted (other file types are out of scope), and that the compiled `DEBUG`
arguments are required and neither has a default: the expected mode is stated state of the bundles in it is the one that was asked for. Both arguments are
by the caller rather than read from `AUTISTMASK_DEBUG`, and the file list required and neither has a default: the expected mode is stated by the caller
comes from the build's receipt rather than from `dist/` (see rather than read from `AUTISTMASK_DEBUG`, and the file list comes from the
build's receipt rather than from `dist/` (see
[Build Receipts](#build-receipts)). Run automatically at the end of [Build Receipts](#build-receipts)). Run automatically at the end of
`make build` and `make build-debug`; fails loudly rather than passing whenever `make build` and `make build-debug`; fails loudly rather than passing whenever
it cannot determine something. Not part of `make check`, which does not depend it cannot determine something. Not part of `make check`, which does not depend

13
TODO.md
View File

@@ -26,7 +26,8 @@ milestone is in flight on `next`; its `next` -> `main` PR is
[#190](https://git.eeqj.de/sneak/AutistMask/pulls/190). `make check` verified [#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 green on `next` at `e9fa8be` on 2026-08-10, and `make build` produces
`dist/chrome/` and `dist/firefox/`, verified against the build's own receipt to `dist/chrome/` and `dist/firefox/`, verified against the build's own receipt to
be exactly what that build emitted with `DEBUG` compiled off. hold exactly the regular files and symlinks that build emitted, with `DEBUG`
compiled off.
The backlog lives on the The backlog lives on the
[Gitea tracker](https://git.eeqj.de/sneak/AutistMask/issues), which is [Gitea tracker](https://git.eeqj.de/sneak/AutistMask/issues), which is
@@ -44,6 +45,16 @@ but the review is broader than any of them.
# Completed Steps # Completed Steps
- 2026-08-23: `README.md` and `script/verify-build`'s own comments now state the
emitted-tree guarantee at the width the code actually enforces
([#331](https://git.eeqj.de/sneak/AutistMask/issues/331)). The tree walk is
`-type f -o -type l`, so the guarantee covers regular files and symlinks under
`dist/`; fifos, sockets, device nodes and empty directories are not checked,
because a build emits none of them, none can carry a shippable payload, and
`grep` on a fifo would hang rather than fail. The exclusion is deliberate and
unchanged — the README said "nothing under `dist/` that the build did not
write", which was broader than that. Documentation only; no executable line
changed.
- 2026-08-20: A second extension page can no longer silently delete a wallet - 2026-08-20: A second extension page can no longer silently delete a wallet
([#304](https://git.eeqj.de/sneak/AutistMask/issues/304)). `saveState()` wrote ([#304](https://git.eeqj.de/sneak/AutistMask/issues/304)). `saveState()` wrote
the entire state blob, and every extension page — the toolbar popup, a dApp the entire state blob, and every extension page — the toolbar popup, a dApp

View File

@@ -1,8 +1,10 @@
#!/bin/sh #!/bin/sh
# script/verify-build: assert that dist/ holds exactly what the build that just # script/verify-build: assert that the regular files and symlinks under dist/
# ran emitted, and that the compiled DEBUG state of that output is the one the # are exactly what the build that just ran emitted (other file types are out of
# caller asked for. Our own extension to scripts-to-rule-them-all, run at the # scope; see "What that does and does not establish" below), and that the
# end of make build / make build-debug. # compiled DEBUG state of that output is the one the caller asked for. Our own
# extension to scripts-to-rule-them-all, run at the end of make build /
# make build-debug.
# #
# Why the DEBUG half exists: DEBUG makes the publicly committed test recovery # Why the DEBUG half exists: DEBUG makes the publicly committed test recovery
# phrase the output of wallet creation, so a release artifact built with it live # phrase the output of wallet creation, so a release artifact built with it live
@@ -29,12 +31,16 @@
# path fresh per invocation, outside the repo, and deletes it afterwards. # path fresh per invocation, outside the repo, and deletes it afterwards.
# #
# What that does and does not establish. It establishes that dist/ is byte for # What that does and does not establish. It establishes that dist/ is byte for
# byte the output of the build.js run that just finished, with nothing added, # byte the output of the build.js run that just finished, with no regular file
# nothing missing and nothing altered in between, and that the audited bundles # or symlink added, missing or altered in between, and that the audited bundles
# in it compiled to the requested mode. It does NOT establish that the source # in it compiled to the requested mode. Regular files and symlinks are the whole
# tree or build.js were honest, and it says nothing at all to someone handed a # of what the tree walk covers; fifos, sockets, device nodes and empty
# dist/ from elsewhere: without the receipt from its own build they have no # directories under dist/ are not checked, because a build emits none of them,
# input to this check. That is signing, and it is not this control. # none can carry a shippable payload, and grep on a fifo would hang rather than
# fail. It does NOT establish that the source tree or build.js were honest, and
# it says nothing at all to someone handed a dist/ from elsewhere: without the
# receipt from its own build they have no input to this check. That is signing,
# and it is not this control.
# #
# It fails rather than passes whenever it cannot determine something. Minified # It fails rather than passes whenever it cannot determine something. Minified
# output is not a stable contract, so "matched neither marker" is not evidence # output is not a stable contract, so "matched neither marker" is not evidence
@@ -392,8 +398,10 @@ check_receipt_entries() {
# its own command line, so a linked dist/ collapses this walk to one entry # its own command line, so a linked dist/ collapses this walk to one entry
# and cross-checks nothing. # and cross-checks nothing.
# #
# Types other than regular files and symlinks are left out on purpose: a build # Types other than regular files and symlinks — fifos, sockets, device nodes and
# emits none of them, and grep on a fifo would hang rather than fail. # empty directories — are left out on purpose, and the guarantee is bounded to
# what is walked: a build emits none of them, none can carry a shippable
# payload, and grep on a fifo would hang rather than fail.
check_dist_tree() { check_dist_tree() {
LISTING="$(mktemp "${TMPDIR:-/tmp}/verify-build-dist.XXXXXX")" || LISTING="$(mktemp "${TMPDIR:-/tmp}/verify-build-dist.XXXXXX")" ||
fail "could not create a temporary file for the dist/ listing, so the fail "could not create a temporary file for the dist/ listing, so the