9 Commits
Author SHA1 Message Date
thePR0M3TH3ANandClaude Fable 5 f0b5f40fd4 Audit response: fix BL-16 through BL-20 and the low findings, same day
BL-16 (high): a remote signer's auth_url is attacker-controlled input that
became a clickable href. sanitizeAuthUrl in core now admits only https
(or loopback http) -- javascript:/data:/blob:/custom schemes are dropped
without consuming the surfacing slot -- and the widget link shows the
hostname. Hostile-scheme tests at both layers.

BL-17: the post-creation PRF fallback assertion is pinned via
allowCredentials to the credential that was just created, so a multi-
passkey account sheet can no longer silently bind an older passkey's
account.

BL-18: queryOnce's timeout path sent CLOSE before settling, unguarded --
a relay dying mid-query stranded the promise and everything queued behind
the worker's serial queue. The send is guarded; settling is unconditional.
MockRelay.close() now terminates live sockets (it previously waited on
them forever), and a suppressEose switch models the silent relay.

BL-19: relays are no longer trusted to honor limits. Buffered queries
enforce filter.limit locally, drop replayed duplicate ids, and cap at
1000 events; a 20-copy replay now yields one event.

BL-20: the .semgrep.yml try/catch fallback pattern never parsed, so the
entropy rules silently did not run. Rewritten in parseable form and
verified under the pinned Semgrep 1.171.0: fires on hostile code, exits
0 on the clean tree.

Lows: NIP-07 and NIP-46 verify returned signed events (signature, pinned
session identity, field equality with the request); Nip46Client.close()
wipes the client and conversation keys (§11.10); passkey labels carry a
short suffix so multiple accounts are distinguishable; postcss bumped to
8.5.25 (GHSA-fxqj-rqcc-2cmp); GitHub Actions pinned to commit SHAs.
Deferred as interop (recorded in TODO): NIP-46 protocol logout and
switch_relays. 12 new adversarial tests; 256 total.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-04 13:10:20 -04:00
thePR0M3TH3ANandClaude Fable 5 36cd3eb853 Sync dev docs with today's work: README, CHANGELOG, TODO, llms.txt cleanup
README now presents the widget as the complete login surface (four
methods, method-aware events, window.bitlogin session API) and retires
the stale 'NIP-46/NIP-07 interop deferred' line. CHANGELOG gains a
2026-08-04 entry -- leading with the 'wasm-unsafe-eval' CSP requirement
and the service-worker cache:'reload' pattern, since a vendored site
without them has broken password flows. TODO records the shipped
expansion and the owner follow-ups: live-fire Amber QR and real-device
passkey tests, the new passkey derivation compatibility contract, and
the unchanged NIP-46 persistence gate. Removes a dangling on-ramp
fragment my earlier llms.txt edit left fused to the passkey bullet.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-04 12:58:32 -04:00
thePR0M3TH3ANandClaude Fable 5 6b48de6168 Widget: offerNwcConnection() -- consent-gated offer-to-save
The inverse of requestNwcConnection: an app that obtained an NWC URI by
its own means (its own wallet chooser, its own paste box) offers the
user a portable copy. The credential is already in the app's hands, so
the only question on screen is whether a copy enters the user's vault
-- and that write happens in BitLogin's own chrome or not at all, never
silently, because a page that can pump records into someone's account
without a tap is a spam vector wearing a feature's clothes.

Dedupe by connection identity (wallet pubkey + secret; relays and
labels may drift without making a new connection -- sameNwcCredential
in core): a duplicate resolves 'already-saved' with a silent
origin-binding refresh and no UI, which is what makes the call safe to
fire after every successful connect. 'unavailable' (no session, no
vault root) is a quiet no-op by contract.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CMr9Yt8SYmZcfgrg3CV2G1
2026-07-30 21:17:06 -04:00
thePR0M3TH3ANandClaude Fable 5 c3e55add16 Widget: requestNwcConnection() -- vault authorization layer, reveal mode
The Phase D surface from vault-ux.md, scoped to NWC and honest about its
boundary: an embedded same-origin widget cannot broker (§CV12.3), so the
API reveals, says so in its consent copy, and leans on the wallet-side
budget as the real security boundary.

One public call owns the whole ceremony. If nobody is signed in, the
sign-in flow runs first and the request resumes via the goto("dashboard")
hook exactly once; if the account has a connection bound to the
requesting origin, the user gets the one-tap approval screen the vault
exists for; otherwise a guided import -- Bitcoin Connect's wallet
chooser or a pasted URI. The BC integration lifts Satisfied's
production discipline verbatim: chooser only, lazy chunks (never
downloaded unless the flow opens), no numeric budget in
authorizationUrlOptions (the unit is not interoperable), disconnect and
clear bc:config so no plaintext copy outlives the ceremony.

The worker holds the vault root (never the sudo key -- wiped on
arrival) and gains vaultStatus/List/SaveNwc/FindForOrigin/RevealNwc/
SetBinding/Delete actions; the root rides the encrypted session cache
so restored sessions keep working, with a vaultEnabled marker so a
stale cache says "sign in again" instead of falsely claiming the
account has no vault. Publishing failures are loud -- a connection that
reached no relay is not saved anywhere and says so. The dashboard gains
Wallet connections management with "revoke app access" and "remove
from BitLogin" honestly separated from wallet-side revocation.
VaultSession gains buildDeletionRequest so the NIP-09 courtesy ask is
signed without exposing the signing key.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CMr9Yt8SYmZcfgrg3CV2G1
2026-07-30 13:10:39 -04:00
thePR0M3TH3ANandClaude Fable 5 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
2026-07-30 12:29:46 -04:00
thePR0M3TH3ANandClaude Sonnet 5 681cec7734 Document session persistence in README and CHANGELOG
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DY9UMTuF43Ekc2U4E1eDwX
2026-07-22 07:47:49 -04:00
thePR0M3TH3ANandClaude Sonnet 5 92a87ff13e CHANGELOG: document the NIP-04 addition
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DY9UMTuF43Ekc2U4E1eDwX
2026-07-21 23:29:10 -04:00
thePR0M3TH3ANandClaude Sonnet 5 e3270012e9 CHANGELOG: document the style-src CSP fix
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DY9UMTuF43Ekc2U4E1eDwX
2026-07-21 22:09:45 -04:00
thePR0M3TH3ANandClaude Sonnet 5 863f035990 Add CHANGELOG.md and an "Updating your integration" README section
Vendoring sites had no documented way to learn a fix landed here or how
to pull it in. Adds a changelog (integrator-relevant entries only, each
noting whether it applies to a site's config) and a README section
explaining why vendoring is manual by design -- a shared auto-updating
script would make a bad deploy here an instant multi-site incident for
a widget that handles private key crypto.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DY9UMTuF43Ekc2U4E1eDwX
2026-07-21 21:31:45 -04:00