mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-11-02 19:41:22 +00:00
ba49673ef9fd457cc1cef1ab838d95daadcd63b2
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
b414cc53d2 |
Security audit: fix 8 findings across crypto, vault, and widget
An adversarial audit (five independent reviewers over crypto/protocol, the vault, the widget, relay flows, and doc honesty) found real bugs in code this repo shipped. Fixed here, worst first: CRITICAL - the widget worker ignored application_binding.origin on vaultRevealNwc, so any caller naming a connection id could pull a wallet credential the user granted to a DIFFERENT site. The worker now derives its own origin (self.location) instead of trusting a payload field -- which also closes binding forgery via vaultSaveNwc / vaultSetBinding / vaultOfferCheck, where a host could bind another site's wallet to itself and inherit its one-tap approval. CRITICAL - VaultSession.destroy() left the vault root live (the root regenerates every derived key) and, worse, left the session USABLE with a zeroed prk: deriveRecordKey/deriveVaultSigningKey over 32 zero bytes are deterministic and world-computable, so a stale reference could seal a wallet secret under a key any observer can derive and report success. Now wipes the root and fails loudly. CRITICAL - changePassword published the tombstone that DESTROYS the old capsule before checking that the new one reached quorum: a failed rotation could leave neither password working, phrase-only recovery. Reordered to fail closed with the old password intact, plus a regression test that drives it with a partially-hostile relay set. HIGH - enableConnectionVault never checked quorum on the recovery read, so one stale relay could make it mint a SECOND vault root and orphan every stored credential -- the outcome its own header calls unforgivable. HIGH - the rollback high-water mark used strict `<` and never compared the event id it stored, so a same-second sibling of a revocation could resurrect a revoked credential forever. Ties now break on event id. HIGH - §11.10 claimed derived secrets are wiped; wipe() had two callers and neither was on the password or recovery path. The Argon2id output, both PRKs, and the 64-byte BIP-39 seed now die at their derivation site. MEDIUM - one malformed record threw out of the listing loop and made every OTHER connection unlistable; recovery lacked the already-registered-at-this-locator guard that registration and rotation both carry; the recovery chain check silently passed on gaps and forks. Synthetic (untrusted) clicks and submits no longer drive consent UI. Docs corrected where they overstated: the README's "private keys never leave the worker" now says plainly that a same-origin host can reach exportIdentity, and connection-vault.md gains §19 recording six OPEN limitations -- no CAS on capsule writes, per-device-only rollback state, tombstones not destroying prior ciphertext, the sudo key shipping with the root, fetches not failing closed, and a plaintext mark store. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01CMr9Yt8SYmZcfgrg3CV2G1 |
||
|
|
a465e1d8fa |
Implement the Connection Vault protocol layer (phases A-C)
connection-vault.md 5-13 as code, with the implementation-time decisions recorded in a new section 18 and vault-ux.md holding the agreed UX walkthroughs as the authorization layer's acceptance criteria. Capsules gain paired optional fields connection_vault_root and vault_sudo_key (both or neither, validated); registration mints them, login/change-password/recovery carry them forward, and enableConnectionVault is the phrase-gated migration for existing accounts -- recovery capsule written first, fail closed, idempotent by reusing roots found in either capsule so a partial failure can never mint twice and strand records. src/vault/ holds the finalized key hierarchy (HKDF/ScalarExpand constants pinned by test vectors that must only ever be reverted, never updated), per-connection encrypted records signed by a derived vault identity with the capsule JCS+padding+AES-GCM pipeline and shared buckets so record types are indistinguishable by size, the NWC profile with lossless URI parse/export, relay sync reusing the publish-and- verify quorum bar, and per-record high-water marks so a stale replica is withheld and reported rather than silently resurrecting a revoked credential. The two-tier model makes sudo mode cryptographically real: personal- tier record keys require vault_sudo_key as HKDF input, VaultSession holds it only inside an enableSudo/endSudo window (wiping on the way in and out), and outside a window personal records are invisible -- the same trial-decrypt returns null, not an error. The app-facing API is structurally blind to the tier by construction. 118 tests passing, including four end-to-end scenarios against mock relays: clean-device restore, password-change carry, legacy-account migration (wrong phrase refused), and rollback detection. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01CMr9Yt8SYmZcfgrg3CV2G1 |
||
|
|
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 |