mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-10-31 02:21:22 +00:00
Neither remaining Math.random call was a secret. The backup-quiz picker chooses which three recovery words to re-type -- an attacker who cannot see the phrase gains nothing from predicting them -- and the relay subscription id is a public wire identifier scoped to one socket, where collision is the only failure mode. They are changed anyway, because the point is the rule rather than these two call sites. A Math.random ban that carries a standing exception is worth much less than one that does not: the exception is what a future misuse hides behind, and reviewers stop reading a warning they have already agreed to ignore twice. Both now use the CSPRNG (randomUniformInt, and bytesToHex(randomBytes(8)) for the subscription id, same 16-hex-character shape as before), which costs nothing here. With no exception left, the rule can be absolute. .semgrep.yml adds three rules over packages/*/src/**, run in CI as a separate step after --config auto: - bitlogin-no-math-random: no Math.random anywhere, secret or not - bitlogin-no-crypto-fallback: no `?? fallback`, `|| fallback`, or catch-and-substitute around getRandomValues -- the COLDCARD defect class - bitlogin-no-time-or-id-seeded-secrets: no hashing Date.now() or performance.now() into a key Verified the rules fire rather than merely passing, which is the failure mode of a lint rule nobody tests: reintroducing the old Math.floor(Math.random() * pool.length) produces 1 blocking finding and exit 1, and the tree is clean at 0 findings with it restored. Path patterns are anchored (/packages/...) so semgrep's upcoming Semgrepignore v2 change does not silently alter their scope. Typecheck and the full suite pass (185 tests). This closes the entropy audit for this repo. The only gate remaining is an independent review of the RNG path by someone other than the author. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01HtNMsKzzFTPURemX7tT19u
96 lines
4.9 KiB
Markdown
96 lines
4.9 KiB
Markdown
# BitLogin — open items
|
|
|
|
Updated: 2026-07-31
|
|
|
|
## Entropy audit (2026-07-31)
|
|
|
|
[`entropy-audit-2026-07-31.md`](./entropy-audit-2026-07-31.md) — RNG and
|
|
randomness-integration audit. **No predictable-fallback defect:** `random.ts`
|
|
is the single chokepoint, throws rather than degrading, and rejection-samples to
|
|
avoid modulo bias. Three hardening items, in order:
|
|
|
|
- **M2 — done.** Nine tests in `packages/core/src/crypto/crypto.test.ts` pin
|
|
fail-closed behaviour and `randomUniformInt`'s uniformity. Mutation-verified:
|
|
a zero-filling stub in place of the throw turns five of them red, and
|
|
swapping rejection sampling for bare modulo gives chi-square 5614 against a
|
|
threshold of 147.
|
|
- **M1 — done.** `nip44Encrypt` / `nip04Encrypt` no longer take a nonce or IV;
|
|
the capability lives in the non-exported `crypto/testing.ts`, and
|
|
`crypto/index.ts` re-exports explicitly instead of `export *`. Only one test
|
|
ever used an override and `ivOverride` had no callers, so nothing was lost.
|
|
Verified from a real consumer against the built package: all three deep-import
|
|
routes fail with `ERR_PACKAGE_PATH_NOT_EXPORTED`. `knownAnswer.test.ts` green
|
|
throughout — the derivation chain is untouched.
|
|
- **L1 — done.** Both `Math.random` calls (backup-quiz picker, relay subscription
|
|
id) now use the CSPRNG, so the ban has no standing exception. `.semgrep.yml`
|
|
enforces it in CI alongside two more rules: no fallback around
|
|
`getRandomValues`, and no timestamp-seeded secrets. Verified the rules fire —
|
|
reintroducing the old call gives 1 blocking finding and exit 1.
|
|
|
|
**The entropy audit is closed for this repo.** The only gate left is an
|
|
independent review of the RNG path by someone other than the author, which is
|
|
not something a coding agent can sign off.
|
|
|
|
## Owner action items
|
|
|
|
1. **Close the Dependabot PR on
|
|
`dependabot/npm_and_yarn/npm-security-and-maintenance-83024bc549`.** It
|
|
bundles seven major bumps into one change (`@noble/hashes` 1.8→2.2,
|
|
`@noble/curves` 1.9→2.2, `@scure/base` 1.2→2.2, `@scure/bip39` 1.6→2.2,
|
|
TypeScript 5→7, Vite 6→8, Vitest 3→4) and fails the build: noble v2 dropped
|
|
the extensionless subpath exports this code imports. It should be closed,
|
|
not merged or fixed — `.github/dependabot.yml` now groups only minor/patch
|
|
and ignores majors of the derivation chain, so this PR will not recur.
|
|
|
|
The four `dependabot/github_actions/*` PRs are unaffected and fine to merge.
|
|
|
|
2. **Decide whether the manual-password removal is final.** User-chosen
|
|
passwords were removed entirely (not merely discouraged) in the 2026-07-31
|
|
hardening, and `packages/widget/src/securityPolicy.test.ts` pins that. The
|
|
security argument is sound — a user-chosen password against a downloadable
|
|
capsule is offline-guessable forever — but it is a product decision that
|
|
changes what users can do, and it is now enforced by a test rather than by
|
|
preference.
|
|
|
|
## Standing rule: the derivation chain is a compatibility contract
|
|
|
|
Account capsules live on public relays. The login name plus password derive the
|
|
locator, signing scalar and capsule key deterministically, so those outputs are
|
|
a contract with every account that already exists — change them and users are
|
|
locked out permanently, with no server-side record to migrate from.
|
|
|
|
`packages/core/src/crypto/knownAnswer.test.ts` pins HKDF-Extract, HKDF-Expand,
|
|
ScalarExpand (scalar, counter, and resulting public key), BIP-39 phrase and
|
|
seed, base64url framing, and JCS ordering.
|
|
|
|
**A failure there is never "update the expected value".** It means the change
|
|
being attempted is account-breaking and needs a migration plan or rejection.
|
|
The rest of the crypto suite only checks self-consistency — same input, same
|
|
output within one run — which stays green through exactly the kind of upgrade
|
|
that would lock everyone out.
|
|
|
|
## Deferred hardening
|
|
|
|
Carried from the 2026-07-30 audit; none are live defects.
|
|
|
|
- **Tier 2** — `packages/widget/src/element.ts` is ~1,600 lines with no direct
|
|
test coverage. `securityPolicy.test.ts` now asserts a few properties of its
|
|
source text, which is a start, not coverage.
|
|
- **Tier 3** — compare-and-swap on capsule writes (relays offer no atomic
|
|
primitive, so this needs a protocol-level answer), and an encrypted
|
|
high-water-mark store.
|
|
- **Tier 4** — moving high-value authorization into a cross-origin or extension
|
|
boundary. An embedded widget is same-origin with its host page, so a
|
|
compromised host can reach the worker directly. Documented honestly in
|
|
`llms.txt` and the README rather than papered over.
|
|
|
|
## Gates still in force
|
|
|
|
- **External security review before the vault holds anything beyond a
|
|
budget-capped NWC connection.** The S3 and password profiles stay designs
|
|
until then.
|
|
- Personal-tier UI was gated on the sudo key leaving the credential capsule.
|
|
That gate is now **met** — the sudo key lives only in the phrase-gated
|
|
recovery capsule — but the UI still needs a separately reviewed key
|
|
ceremony before it ships.
|