harden: keep the test recovery phrase out of release bundles, match committed keys by content (closes #351)
check / check (push) Failing after 1s
e2e / e2e-chrome (push) Failing after 1s
e2e / e2e-firefox (push) Failing after 1s

The 12-word BIP-39 test phrase survived in every release bundle as dead
text: module.exports keeps DEBUG_MNEMONIC live even though wallet.js's only
use of it folds away in a release build, so it could not be tree-shaken.
Putting the value itself behind the __BUILD_DEBUG__ define makes esbuild fold
it to null, so no distributed bundle carries it. script/verify-build now
fails a release build if the phrase appears in any emitted file, so the fold
cannot silently regress; test-verify-build covers both the release failure
and the debug allowance.

tests/extensionId.test.js now scans the content of every tracked file for a
PEM private-key header instead of matching filename extensions alone, so a
key committed under an unexpected name is caught.

Model: opus-4-8
This commit is contained in:
2026-09-21 07:55:39 +00:00
parent 99292b9188
commit 84be04d6e9
6 changed files with 100 additions and 11 deletions
+17 -8
View File
@@ -89,19 +89,28 @@ describe("chrome extension identity", () => {
// The private half is a credential. It has never been in this repo and no
// target generates one into the working tree; this fails loudly if that
// ever changes, because a committed .pem is a key anyone can sign a CRX
// with under this extension's id.
// ever changes, because a committed private key is one anyone can sign a
// CRX with under this extension's id.
//
// Matched by CONTENT, not by filename: a key committed as notes.txt or with
// no extension carries the same risk as one named key.pem, and a
// filename-only check waves it through. The PEM header a private key opens
// with is the signature searched for. The pattern does not trip on its own
// source: the bracket-expression characters between the two anchors are not
// in the character class, so this file is not a match for it.
const PRIVATE_KEY_HEADER = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/;
test("no private key is committed anywhere in the tree", () => {
const root = path.join(__dirname, "..");
const tracked = require("child_process")
.execSync("git ls-files", {
cwd: path.join(__dirname, ".."),
encoding: "utf8",
})
.execSync("git ls-files", { cwd: root, encoding: "utf8" })
.split("\n")
.filter(Boolean);
expect(tracked.filter((f) => /\.(pem|key|p12|pfx)$/i.test(f))).toEqual(
[],
const offenders = tracked.filter((f) =>
PRIVATE_KEY_HEADER.test(
fs.readFileSync(path.join(root, f), "latin1"),
),
);
expect(offenders).toEqual([]);
});
});
+8
View File
@@ -27,6 +27,14 @@ describe("generateMnemonic in a release build", () => {
expect(constants.DEBUG).toBe(false);
});
test("the test phrase folds away when DEBUG is false", () => {
// The release bundle is what must not carry the phrase; here, with the
// define absent, DEBUG_MNEMONIC is the null branch the bundler keeps,
// and the literal only exists in the branch it drops.
const { constants } = loadWallet();
expect(constants.DEBUG_MNEMONIC).toBeNull();
});
test("returns fresh, valid 12-word phrases that are not the test phrase", () => {
const { constants, wallet } = loadWallet();