Skip to content

Key backup from Settings works for every login; 4-digit PINs behind a server-side lockout (0.5.0) - #283

Merged
ralyodio merged 2 commits into
masterfrom
fix/key-backup-pin
Oct 6, 2026
Merged

ralyodio merged 2 commits into
masterfrom
fix/key-backup-pin

Conversation

@ralyodio

@ralyodio ralyodio commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Fixes "can't backup keys from settings page" and allows 4-digit backup PINs, as Anthony asked.

Why backup failed: Settings sent backup/PIN calls with cookies only. Passkey, CoinPay and phone sessions live in localStorage with no cookie, so the server answered 401. The login-time restore (restoreKeysFromServer) had the same problem.

4-digit PINs, safely: before this, any session could GET the encrypted backup and try all 10k PINs offline. Now:

  • GET /api/auth/key-backup only reports existence and timestamps.
  • Restore is POST {pin}, checked against the scrypt hash. After 5 wrong PINs restore locks for 15 min; each further lock doubles, up to 24 h (Retry-After). The failure counter is claimed with compare-and-set before hashing, so parallel guesses cannot slip past it.
  • A backup write must use the account PIN; when none is set, the write adopts it.
  • Changing a verifiable PIN needs the current one, and Settings re-encrypts the server backup with the new PIN.
  • Legacy rows (5 of 6 on dev2 have no verifiable hash) still restore. The client then registers the PIN it just decrypted with, which upgrades the row.
  • Adds DELETE /api/auth/key-backup, which Complete Key Reset was already calling.

Remaining risk: if the database leaks, a 4-digit PIN falls to about 10k scrypt runs. The UI recommends 6+ digits.

Migration 20261006220000_backup_pin_lockout.sql is already applied on dev2 and PostgREST is reloaded.

Tests: 661/661 vitest, including new lockout/race tests and gated-restore route tests; next build passes.

Note: messaging/crypto is Preshy's area; Anthony asked for this directly.

🤖 Generated with Claude Code

ralyodio and others added 2 commits October 6, 2026 09:35
…a server-side lockout

- Settings, restore and key reset send the Bearer session (passkey, CoinPay
  and phone sessions have no cookie, so backup silently 401'd).
- PINs may be 4-12 digits. GET /api/auth/key-backup no longer returns the
  encrypted keys; restore is POST {pin}, checked against the scrypt hash with
  5 tries then 15 min, doubling to 24 h. Backups must use the account PIN.
- Changing a verifiable PIN needs the current one; the server backup is
  re-encrypted with the new PIN.
- Legacy backups (no verifiable hash) still restore; the client registers the
  PIN once it decrypts, upgrading them.
- DELETE /api/auth/key-backup exists now (Complete Key Reset called it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

13 finding(s)

MEDIUM: 9 | LOW: 4

Severity Rule Location
MEDIUM redos-nested-quantifier src/app/api/profile/update/route.js:73
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.jsx:38
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.jsx:66
MEDIUM js-unescaped-html-sink src/app/faq/page.jsx:57
MEDIUM js-unescaped-html-sink src/app/layout.jsx:137
MEDIUM js-unescaped-html-sink src/app/layout.jsx:141
MEDIUM js-unescaped-html-sink src/app/page.jsx:47
MEDIUM redos-nested-quantifier src/lib/auth/dns-name.js:88
MEDIUM js-unescaped-html-sink src/lib/components/chat/MessageItem.jsx:121
LOW secret-generic-credential src/app/api/auth/register-anon/route.test.js:32
LOW secret-jwt tests/debug-sms.js:10
LOW secret-generic-credential tests/private-key-import-export.test.js:252
LOW secret-generic-credential tests/private-key-import-export.test.js:264

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 795a7b0 into master Oct 6, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant