security: make DEBUG a build-time flag defaulting to off (closes #149)
All checks were successful
check / check (push) Successful in 29s

DEBUG was hardcoded to true in src/shared/constants.js, so every wallet
created from a build of main received the publicly committed test recovery
phrase and was instantly drainable. There was no way to produce a non-debug
build at all: the real entropy path in generateMnemonic() was dead code in
every artifact.

DEBUG is now a build-time constant injected by esbuild's define in build.js,
alongside the existing __BUILD_* defines, and read by constants.js with the
same typeof guard buildInfo.js uses. It is false unless the build was run
with AUTISTMASK_DEBUG=1 — an exact match, so an unset, empty or mistyped
value fails safe towards a release build. The build prints which mode it
used, and make build-debug is a shim for the debug case.

What DEBUG does when enabled is unchanged: the red banner plus the hardcoded
test phrase, no new conditionals. Mnemonic generation deliberately keeps
reading the compile-time constant rather than isDebug() from log.js, which
also ORs in the runtime debugMode flag the settings toggle drives; routing it
through isDebug() would let a user of a release build re-enable the known
test phrase for real wallets. That is now recorded at the call site, in the
README DEBUG Mode Policy, and covered by a regression test.

New tests/wallet.test.js covers both build modes: with the flag off, two
successive generateMnemonic() calls differ, both validate as BIP-39 phrases,
both are 12 words, neither is DEBUG_MNEMONIC, and the result still derives a
usable HD wallet — including with the runtime toggle forced on. With the flag
on, DEBUG is true and the test phrase is returned, so the debug path stays
proven rather than silently removed.

Verified with make check (55 tests, lint, fmt-check all green), and with
make build and make build-debug: all four bundles across dist/chrome and
dist/firefox export DEBUG:!1 in a release build and DEBUG:!0 in a debug
build, and AUTISTMASK_DEBUG=true likewise yields DEBUG:!1.
This commit is contained in:
2026-08-09 01:55:13 +00:00
parent 23aeae4841
commit acb58856c4
7 changed files with 207 additions and 21 deletions

View File

@@ -42,6 +42,23 @@ Load the extension:
- **Firefox**: Navigate to `about:debugging#/runtime/this-firefox`, click "Load
Temporary Add-on", and select `dist/firefox/manifest.json`.
### Debug Builds
`make build` always produces a release build: the build-time `DEBUG` constant is
`false`, so wallet creation uses real entropy and the red banner is off. To
produce a debug build instead, set `AUTISTMASK_DEBUG=1` in the environment:
```bash
make build-debug # or: AUTISTMASK_DEBUG=1 make build
```
Only the exact value `1` enables it; any other value (including unset, empty, or
`true`) yields a release build, so a typo cannot accidentally ship the debug
behavior. The build prints which mode it used. See the
[DEBUG Mode Policy](#debug-mode-policy) for what the flag changes. **Never
distribute a debug build** — every wallet it creates gets the same publicly
known test recovery phrase.
## Entrypoints
This repository adheres to the
@@ -668,6 +685,19 @@ flows, or alter program behavior beyond the banner and the hardcoded mnemonic.
Adding new DEBUG-conditional branches requires explicit approval from the
project owner.
`DEBUG` is a build-time constant, not a runtime setting. `build.js` injects it
into the bundle as the `__BUILD_DEBUG__` define — `false` unless the build was
run with `AUTISTMASK_DEBUG=1` (see [Debug Builds](#debug-builds)) — and
`src/shared/constants.js` reads it. It cannot be changed after the bundle is
produced.
The debug-mode toggle in settings is a separate, runtime-only flag. It raises
the log level and turns the banner on, and that is all it may ever do: it feeds
`isDebug()` in `src/shared/log.js`, which is deliberately not what
`generateMnemonic()` consults. Mnemonic generation reads the build-time `DEBUG`
constant directly, so no runtime toggle in a release build can reach the
hardcoded test phrase.
### Key Decisions
- **No framework**: The popup UI is vanilla JS and HTML. The extension is small