Commit Graph
5 Commits
Author SHA1 Message Date
thePR0M3TH3ANandClaude Fable 5 fa9e2d9665 Automated passkey e2e: real Chromium, real WebAuthn ceremonies, virtual PRF
Discharges most of the 'live-fire passkey on a real device' TODO without
hardware. Playwright drives headless Chromium with a CDP virtual
authenticator (hasPrf: true) against the built demo served locally and
pointed at three in-process mock relays, so the test is hermetic -- no
public relays, no platform passkey provider, no network.

Covered end to end: navigator.credentials.create, PRF extension results
crossing the browser boundary, worker-side derivation, account
registration (asserted at the PROTOCOL layer -- kind-30078 capsules
really land on the relays, not just a UI state), the Tier B2 phrase card
appearing for a first session and not a returning one,
window.bitlogin.activeSession() reporting, logout, then
navigator.credentials.get signing back in to the SAME npub. A second
test proves PRF is per-credential: a different passkey derives a
different account.

Two things the harness taught us, both now encoded: WebAuthn rejects a
bare 127.0.0.1 as an RP id (localhost only), and the options menu's
disclosure state survives re-renders by design, so the driver opens it
conditionally.

Runs inside npm test and skips itself when the Playwright browser isn't
installed, so CI and other machines stay green. MockRelay gains
storedEventKinds() for protocol-level assertions. Remaining real-device
gap is recorded in TODO: a genuine platform authenticator and the
create-time-PRF-absent fallback, which the virtual authenticator does
not exercise.

268 tests green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-04 14:57:10 -04:00
thePR0M3TH3ANandClaude Fable 5 bb9788588f Audit round 2: resource ceilings, key lifecycle, local gate, courtesy logout
BL-21 (medium): untrusted relays and crafted bunker URIs can no longer
buy CPU, memory, or sockets. Frames over 256K chars are dropped before
JSON.parse; buffered queries run every cheap check (id shape, replay
dedup, capacity) BEFORE Schnorr verification and carry a per-query
verification budget (3x limit, floor 50) so distinct forged events
exhaust the budget rather than the CPU; bunker URIs are deduplicated
and capped at 8 relays. The audit's exact probe -- thousands of replayed
copies against limit 1 -- now costs one verification, bounded by test.
Documented trade: a relay that sprays forgeries past the budget forfeits
the query.

BL-22 (low): pending nostrconnect attempts now have a real lifecycle.
Superseding or abandoning an attempt aborts its 180s listener (new
AbortSignal support) and wipes its ephemeral key; on success the key's
ownership transfers into the adopted client, which wipes on close.
Per-candidate conversation keys in the listen loop are wiped in a
finally instead of left to GC.

BL-23 (low): the named local gate no longer skips static analysis --
security:ci runs the custom Semgrep rules via security:semgrep (same
config and pin as the hosted workflow).

BL-24 (informational): the courtesy protocol logout defined by current
NIP-46 is sent on every teardown path (disconnect, supersession),
bounded at 2s and never allowed to block closing; TODO's outdated 'no
stable method name' note is corrected. switch_relays remains the one
deferred interop item.

6 new adversarial tests; 262 total, all green; semgrep gate exit 0.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-04 13:56:29 -04:00
thePR0M3TH3ANandClaude Fable 5 7d2209d706 Second-audit hardening: phrase-gated sudo key, deterministic migration, policy tests
Authored by ChatGPT/Codex in a separate review pass. Reviewed and verified
here before committing rather than taken on trust; findings below.

The substantive change is that the vault sudo key now lives ONLY in the
phrase-gated recovery capsule. A newly written password credential capsule
carries just connection_vault_root, so an ordinary login restores
connectable-tier records without putting personal-tier material behind a
password capsule that is downloadable and therefore offline-guessable forever.
Pre-hardening capsules stay readable, password login does not return their
sudo key, and the next rotation scrubs it. That closes the gate this project
had explicitly set on personal-tier UI.

Legacy vault-enable migration now derives roots deterministically from
phrase-protected material and account_id, so two concurrent attempts cannot
mint divergent roots -- the failure mode the docs previously mitigated with
"enable the vault once, from one device".

Manual (user-chosen) passwords are REMOVED, not merely discouraged, and a new
securityPolicy.test.ts pins that along with several things tests do not
usually reach: the deployment CSP (default-src 'none', frame-ancestors
'none') plus six baseline headers, demo pages free of inline scripts and
remote fonts, and README documenting same-origin self-hosting only rather
than a remote demo embed. llms.txt was updated to match, which I checked
because that test only reads README.

What I verified, since a passing suite proves less than it appears to:

- All 12 fixes from the 2026-07-30 audit survived the rewrite of recover.ts,
  changePassword.ts, validation.ts, relay.ts, session.ts and element.ts.
  Checked semantically where grep cannot help: changePassword still checks
  publish success BEFORE building the tombstone, and vaultSetBinding still
  unbinds on origin === null rather than silently rebinding to the worker
  global.
- No dependency changes; the package.json diff is scripts only.
- Argon2id and KDF parameters unchanged. The NIP-44 diff ADDS a base64 length
  cap before decoding, a memory-DoS guard.
- Validation increased rather than decreased: 44 throws added, 33 removed.
- No disabled or skipped tests, no secrets in the diff.

typecheck clean, 165/165 tests, build green, and the account manager exercised
in a browser: creation correctly refused when relay quorum could not be met,
and on retry failed at the recovery capsule with "No login credential was
published" -- the §18.1 fail-closed ordering doing its job against a flaky
public relay set.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CMr9Yt8SYmZcfgrg3CV2G1
2026-07-31 13:54:36 -04:00
Claude 9408fef484 Add MIT license, CONTRIBUTING, and SECURITY docs for open-sourcing
Preparing to open the project up beyond this repo's own use:
- LICENSE: MIT, and a "license": "MIT" field on the root and all three
  workspace package.json files.
- CONTRIBUTING.md: dev workflow, the mock-relay testing setup (most
  sandboxes/CI have no route to public Nostr relays, and registration
  needs a 2-of-N ack quorum so a single mock relay always fails), the
  spec-first process for anything touching wire format or crypto
  (docs/spec.md is canonical; speculative ideas go in their own doc, not
  the spec), and stricter expectations for crypto/key-handling changes.
- SECURITY.md: private vulnerability disclosure via GitHub's advisory
  flow rather than public issues, given this handles private keys and
  password-derived secrets client-side; states current audit status
  plainly (none yet).
- README: short "Contributing" section pointing at all three.

Co-Authored-By: Claude <[email protected]>
Claude-Session: https://claude.ai/code/session_01GWJbXXhvTx9wo59jSz57J6
2026-07-21 16:54:55 +00:00
Claude 5ee63ae6b0 Implement BitLogin as a modular protocol library, embeddable widget, and demo site
Builds out the v0.3 spec as an npm workspace monorepo any static site can
consume for login and Nostr signing:

- packages/core: framework-free protocol implementation — Argon2id/HKDF/
  ScalarExpand/AES-256-GCM/JCS crypto primitives, NIP-01 event handling and a
  quorum-aware relay pool, credential/recovery capsule construction and
  validation, and the full account lifecycle (registration, any-device
  login with rollback/relay-disagreement detection, phrase recovery with
  recovery-capsule refresh, password rotation with mandatory tombstone +
  NIP-09 deletion, replica repair, recovery export, NIP-19 encoding, a
  NIP-44 v2 implementation, and a NIP-07-shaped signer). 49 tests cover the
  spec's Phase 0 byte-level vectors plus full create/login/recover/rotate
  end-to-end scenarios against an in-memory mock relay.

- packages/widget: a <bitlogin-auth> custom element with a dedicated crypto
  Web Worker (private keys never touch the main thread) and a window.nostr
  NIP-07-compatible provider, so existing Nostr web apps work against it
  with zero code changes. Built with Vite into two relocatable static files.

- packages/demo: a plain static site (no build step of its own) consuming
  the widget — a branding/landing page, an integration guide, and an
  account-manager page covering create/login/recover, password rotation,
  and identity export.

All flows were verified twice: protocol-level tests against a mock relay,
and the actual built widget driven end-to-end in real Chromium (registration,
phrase confirmation, event signing, clean-device login, window.nostr, and
phrase recovery all reproduce the identical everyday pubkey).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GWJbXXhvTx9wo59jSz57J6
2026-07-21 03:44:10 +00:00