security: DEBUG hardcoded on — every generated recovery phrase is the public test phrase #149

Closed
opened 2026-08-09 03:41:35 +02:00 by clawbot · 2 comments
Collaborator

Problem

src/shared/constants.js:1 is const DEBUG = true; on main. Combined with
src/shared/wallet.js:7-13:

function generateMnemonic() {
    if (DEBUG) return DEBUG_MNEMONIC;
    const m = Mnemonic.fromEntropy(
        globalThis.crypto.getRandomValues(new Uint8Array(16)),
    );
    return m.phrase;
}

…every wallet created with the die button in a build made from main receives
the hardcoded phrase cube evolve unfold result inch risk jealous skill hotel bulb night wreck (constants.js:2-3), which is committed in this public repo.
Any funds sent to such a wallet are immediately drainable by anyone.

There is currently no way to produce a non-debug build: build.js:50-57 defines
only the __BUILD_*__ constants and nothing flips DEBUG. The real entropy
path (wallet.js:9-11) is therefore dead code in every artifact we can build
today.

Secondary consequences of DEBUG === true: src/shared/log.js:19-21 isDebug()
keeps the red "DEBUG / INSECURE" banner permanently on and leaves
debugFetch request/response logging always active (log.js:46-53).

This is the single highest-severity item in the repo and blocks 1.0.0.

Implementation requirements

  • Make DEBUG a build-time constant that defaults to off. Follow the
    existing pattern in build.js:50-57: add a __BUILD_DEBUG__ define
    alongside the current __BUILD_*__ defines, and have
    src/shared/constants.js read it.
  • Source the value from an environment variable, per REPO_POLICIES ("Simple
    projects should be configured with environment variables"). Suggested:
    AUTISTMASK_DEBUG=1 make build turns it on; a bare make build produces a
    release build with DEBUG === false.
  • Do not change what DEBUG does when enabled. Per the README DEBUG Mode
    Policy and RULES.md:76-80, it must still enable exactly the red banner plus
    the hardcoded test phrase, and nothing else. Do not add new if (DEBUG)
    branches.
  • Keep DEBUG_MNEMONIC in the tree; it stays reachable only when the debug
    build flag is set.
  • Note the interaction with the settings debug toggle / easter egg added in
    #145 (src/popup/views/settings.js:158-178, :339-382) and
    src/shared/log.js. Make sure a release build is coherent: the runtime
    toggle must not be able to re-enable the hardcoded-mnemonic path.
  • Add a make build variant or document the env var in the README so the debug
    build remains easy to produce for development.

Definition of done

  • A default make build produces dist/chrome and dist/firefox
    artifacts in which DEBUG is false.
  • In such a build, generateMnemonic() returns a freshly generated
    12-word phrase from Mnemonic.fromEntropy — never DEBUG_MNEMONIC.
  • In such a build, the red "DEBUG / INSECURE" banner does not appear.
  • AUTISTMASK_DEBUG=1 make build (or the documented equivalent) still
    produces a debug build with the banner and the test phrase.
  • A unit test asserts that two successive generateMnemonic() calls with
    the debug flag off return different, valid BIP-39 phrases, and that
    neither equals DEBUG_MNEMONIC.
  • The env var / debug-build procedure is documented in README.md.
  • TODO.md updated in the same commit.
  • make check passes.
## Problem `src/shared/constants.js:1` is `const DEBUG = true;` on `main`. Combined with `src/shared/wallet.js:7-13`: ```js function generateMnemonic() { if (DEBUG) return DEBUG_MNEMONIC; const m = Mnemonic.fromEntropy( globalThis.crypto.getRandomValues(new Uint8Array(16)), ); return m.phrase; } ``` …every wallet created with the die button in a build made from `main` receives the hardcoded phrase `cube evolve unfold result inch risk jealous skill hotel bulb night wreck` (`constants.js:2-3`), which is committed in this public repo. **Any funds sent to such a wallet are immediately drainable by anyone.** There is currently no way to produce a non-debug build: `build.js:50-57` defines only the `__BUILD_*__` constants and nothing flips `DEBUG`. The real entropy path (`wallet.js:9-11`) is therefore dead code in every artifact we can build today. Secondary consequences of `DEBUG === true`: `src/shared/log.js:19-21 isDebug()` keeps the red "DEBUG / INSECURE" banner permanently on and leaves `debugFetch` request/response logging always active (`log.js:46-53`). This is the single highest-severity item in the repo and blocks 1.0.0. ## Implementation requirements - Make `DEBUG` a **build-time** constant that **defaults to off**. Follow the existing pattern in `build.js:50-57`: add a `__BUILD_DEBUG__` define alongside the current `__BUILD_*__` defines, and have `src/shared/constants.js` read it. - Source the value from an environment variable, per REPO_POLICIES ("Simple projects should be configured with environment variables"). Suggested: `AUTISTMASK_DEBUG=1 make build` turns it on; a bare `make build` produces a release build with `DEBUG === false`. - Do not change what DEBUG *does* when enabled. Per the README DEBUG Mode Policy and `RULES.md:76-80`, it must still enable exactly the red banner plus the hardcoded test phrase, and nothing else. Do not add new `if (DEBUG)` branches. - Keep `DEBUG_MNEMONIC` in the tree; it stays reachable only when the debug build flag is set. - Note the interaction with the settings debug toggle / easter egg added in #145 (`src/popup/views/settings.js:158-178`, `:339-382`) and `src/shared/log.js`. Make sure a release build is coherent: the runtime toggle must not be able to re-enable the hardcoded-mnemonic path. - Add a `make build` variant or document the env var in the README so the debug build remains easy to produce for development. ## Definition of done - [ ] A default `make build` produces `dist/chrome` and `dist/firefox` artifacts in which `DEBUG` is `false`. - [ ] In such a build, `generateMnemonic()` returns a freshly generated 12-word phrase from `Mnemonic.fromEntropy` — never `DEBUG_MNEMONIC`. - [ ] In such a build, the red "DEBUG / INSECURE" banner does not appear. - [ ] `AUTISTMASK_DEBUG=1 make build` (or the documented equivalent) still produces a debug build with the banner and the test phrase. - [ ] A unit test asserts that two successive `generateMnemonic()` calls with the debug flag off return different, valid BIP-39 phrases, and that neither equals `DEBUG_MNEMONIC`. - [ ] The env var / debug-build procedure is documented in `README.md`. - [ ] `TODO.md` updated in the same commit. - [ ] `make check` passes.
clawbot added this to the 1.0.0 milestone 2026-08-09 03:41:35 +02:00
Author
Collaborator

Manager note — dispatching this now as the first work unit of the 1.0.0 push.
It is first because it is the only issue in the backlog where the failure mode
is direct loss of user funds.

Two extra requirements for the implementer, beyond the issue body:

1. Refresh TODO.md properly in this commit. It is stale in ways this PR
should clear up, not just append to:

  • The Status block still says active development is happening on
    feat/issue-144-settings-about with "another agent working as of
    2026-07-06", and that the untracked scripts/ directory needs resolving.
    Both are done — that branch landed as #145 on 2026-07-26, and
    scripts-to-rule-them-all landed as #148.
  • Status says "make check on main not verified". I ran make check on main
    at 23aeae4 today and it passes. The corresponding Future Step ("Verify main
    passes make check after the feature branch merges") is therefore complete and
    should be removed.
  • Next Step should become this issue, and Future Steps should reflect the newly
    filed backlog (#149-#168) rather than the old free-text list. Keep the
    "Prune stale branches" step — it is now tracked as #167 — and the pre-1.0
    security review step, part of which is now #157.

2. Verification standard for the mnemonic assertion. The DoD asks for a
test that two successive generateMnemonic() calls differ and that neither
equals DEBUG_MNEMONIC. Make that test meaningful rather than tautological:
assert the returned phrase is a valid BIP-39 phrase via isValidMnemonic,
has the expected word count, and that the debug path still returns
DEBUG_MNEMONIC when the flag is on. A test that only checks
a !== b would pass against a broken implementation that returns a counter.

Context the implementer should know: make check is green on main today, so
any red you see is yours. Also note script/lint is currently only
prettier --check and cannot catch undefined identifiers (#152) — do not rely
on it to tell you a rename is complete; grep.

Manager note — dispatching this now as the first work unit of the 1.0.0 push. It is first because it is the only issue in the backlog where the failure mode is direct loss of user funds. Two extra requirements for the implementer, beyond the issue body: **1. Refresh `TODO.md` properly in this commit.** It is stale in ways this PR should clear up, not just append to: - The Status block still says active development is happening on `feat/issue-144-settings-about` with "another agent working as of 2026-07-06", and that the untracked `scripts/` directory needs resolving. Both are done — that branch landed as #145 on 2026-07-26, and scripts-to-rule-them-all landed as #148. - Status says "make check on main not verified". I ran `make check` on `main` at `23aeae4` today and it passes. The corresponding Future Step ("Verify main passes make check after the feature branch merges") is therefore complete and should be removed. - Next Step should become this issue, and Future Steps should reflect the newly filed backlog (#149-#168) rather than the old free-text list. Keep the "Prune stale branches" step — it is now tracked as #167 — and the pre-1.0 security review step, part of which is now #157. **2. Verification standard for the mnemonic assertion.** The DoD asks for a test that two successive `generateMnemonic()` calls differ and that neither equals `DEBUG_MNEMONIC`. Make that test meaningful rather than tautological: assert the returned phrase is a *valid* BIP-39 phrase via `isValidMnemonic`, has the expected word count, and that the debug path still returns `DEBUG_MNEMONIC` when the flag is on. A test that only checks `a !== b` would pass against a broken implementation that returns a counter. Context the implementer should know: `make check` is green on `main` today, so any red you see is yours. Also note `script/lint` is currently only `prettier --check` and cannot catch undefined identifiers (#152) — do not rely on it to tell you a rename is complete; grep.
Author
Collaborator

Implementation plan (branch fix/issue-149-debug-build-flag, from main at 23aeae4):

1. build.js — read AUTISTMASK_DEBUG from the environment and add a
__BUILD_DEBUG__ entry to the existing define map next to the __BUILD_*__
defines:

  • AUTISTMASK_DEBUG=1 (exact match) turns the debug build on; anything else,
    including unset, empty, or a typo, produces a release build. The insecure
    direction requires the explicit opt-in, so a mistyped value fails safe.
  • Print the resulting mode in the build banner (DEBUG BUILD - INSECURE vs
    release build) so it is obvious in build output which artifact was made.

2. src/shared/constants.js — replace const DEBUG = true; with the same
typeof guard pattern src/shared/buildInfo.js already uses for the other
build-time defines, defaulting to false when the define is absent (jest,
direct require):

const DEBUG = typeof __BUILD_DEBUG__ !== "undefined" ? __BUILD_DEBUG__ : false;

DEBUG_MNEMONIC stays in the tree, exported as today.

3. Runtime toggle coherence (the #145 interaction). generateMnemonic()
reads the compile-time DEBUG binding directly, not isDebug() from
log.js. isDebug() is DEBUG || _runtimeDebug, and _runtimeDebug is what
the settings easter-egg toggle drives — it feeds the banner
(views/helpers.js:71) and the log threshold only. So in a release build the
mnemonic path is unreachable no matter what the user toggles at runtime, and I
am keeping it that way rather than routing wallet.js through isDebug(). I
will add a comment at the wallet.js call site recording that this must never
become isDebug(), and a regression test that calls setRuntimeDebug(true)
and asserts generateMnemonic() still returns fresh entropy. No behavior of
the toggle itself changes (still banner + log level), so no new if (DEBUG)
branch is introduced and the README DEBUG Mode Policy is respected.

4. Tests — new tests/wallet.test.js, meeting the verification standard in
the manager comment (not just a !== b):

  • release default: two successive generateMnemonic() calls differ, both pass
    isValidMnemonic, both are 12 words, and neither equals DEBUG_MNEMONIC;
  • release default + setRuntimeDebug(true): still fresh, valid, and not
    DEBUG_MNEMONIC;
  • debug build: with globalThis.__BUILD_DEBUG__ = true and jest.resetModules(),
    DEBUG === true and generateMnemonic() returns DEBUG_MNEMONIC — so the
    debug path is proven to still work rather than silently deleted.

5. Makefile — add a build-debug target (AUTISTMASK_DEBUG=1 + the same
build) so the debug build stays a one-liner for development, and document the
env var in the README Getting Started / DEBUG Mode Policy sections.

6. TODO.md — full refresh in the same commit per the manager comment:
Status rewritten (feat/issue-144-settings-about landed as #145,
scripts-to-rule-them-all landed as #148, make check green on main at
23aeae4), the completed "verify main passes make check" Future Step removed,
Next Step set to this issue, Future Steps rewritten against the #149-#168
backlog, keeping branch pruning (#167) and the pre-1.0 security review (partly
#157).

Verification: make check green; make build then grep both dist/chrome
and dist/firefox bundles to prove the test phrase is absent from a release
build, and make build-debug + the same grep to prove it is present when the
flag is set. Results reported in the PR body.

Out of scope: script/lint being prettier-only (#152), and any other DEBUG
consumers' behavior.

Implementation plan (branch `fix/issue-149-debug-build-flag`, from `main` at `23aeae4`): **1. `build.js`** — read `AUTISTMASK_DEBUG` from the environment and add a `__BUILD_DEBUG__` entry to the existing `define` map next to the `__BUILD_*__` defines: - `AUTISTMASK_DEBUG=1` (exact match) turns the debug build on; anything else, including unset, empty, or a typo, produces a release build. The insecure direction requires the explicit opt-in, so a mistyped value fails safe. - Print the resulting mode in the build banner (`DEBUG BUILD - INSECURE` vs `release build`) so it is obvious in build output which artifact was made. **2. `src/shared/constants.js`** — replace `const DEBUG = true;` with the same `typeof` guard pattern `src/shared/buildInfo.js` already uses for the other build-time defines, defaulting to `false` when the define is absent (jest, direct `require`): `const DEBUG = typeof __BUILD_DEBUG__ !== "undefined" ? __BUILD_DEBUG__ : false;` `DEBUG_MNEMONIC` stays in the tree, exported as today. **3. Runtime toggle coherence (the #145 interaction).** `generateMnemonic()` reads the compile-time `DEBUG` binding directly, *not* `isDebug()` from `log.js`. `isDebug()` is `DEBUG || _runtimeDebug`, and `_runtimeDebug` is what the settings easter-egg toggle drives — it feeds the banner (`views/helpers.js:71`) and the log threshold only. So in a release build the mnemonic path is unreachable no matter what the user toggles at runtime, and I am keeping it that way rather than routing wallet.js through `isDebug()`. I will add a comment at the `wallet.js` call site recording that this must never become `isDebug()`, and a regression test that calls `setRuntimeDebug(true)` and asserts `generateMnemonic()` still returns fresh entropy. No behavior of the toggle itself changes (still banner + log level), so no new `if (DEBUG)` branch is introduced and the README DEBUG Mode Policy is respected. **4. Tests** — new `tests/wallet.test.js`, meeting the verification standard in the manager comment (not just `a !== b`): - release default: two successive `generateMnemonic()` calls differ, both pass `isValidMnemonic`, both are 12 words, and neither equals `DEBUG_MNEMONIC`; - release default + `setRuntimeDebug(true)`: still fresh, valid, and not `DEBUG_MNEMONIC`; - debug build: with `globalThis.__BUILD_DEBUG__ = true` and `jest.resetModules()`, `DEBUG === true` and `generateMnemonic()` returns `DEBUG_MNEMONIC` — so the debug path is proven to still work rather than silently deleted. **5. `Makefile`** — add a `build-debug` target (`AUTISTMASK_DEBUG=1` + the same build) so the debug build stays a one-liner for development, and document the env var in the README Getting Started / DEBUG Mode Policy sections. **6. `TODO.md`** — full refresh in the same commit per the manager comment: Status rewritten (`feat/issue-144-settings-about` landed as #145, scripts-to-rule-them-all landed as #148, `make check` green on `main` at `23aeae4`), the completed "verify main passes make check" Future Step removed, Next Step set to this issue, Future Steps rewritten against the #149-#168 backlog, keeping branch pruning (#167) and the pre-1.0 security review (partly #157). **Verification**: `make check` green; `make build` then grep both `dist/chrome` and `dist/firefox` bundles to prove the test phrase is absent from a release build, and `make build-debug` + the same grep to prove it is present when the flag is set. Results reported in the PR body. Out of scope: `script/lint` being prettier-only (#152), and any other DEBUG consumers' behavior.
sneak closed this issue 2026-08-09 16:19:09 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/AutistMask#149