Files
Password-Manager/delphi-backend
Zaki a45897c33d feat(security): HIBP password breach check + CSP tightening
HIBP integration
================
Opt-in (default OFF) password breach check via the Have I Been Pwned
range API. The full master / entry password never leaves the machine —
only the first 5 characters of its SHA-1 hash. HIBP returns ~500
candidate suffixes; the client matches its own suffix locally.

UI:
 - New "Check passwords against breach database (HIBP)" toggle in
   Settings → Security with an explainer hint about k-anonymity.
 - On enable: background batch scan of all entries, results cached in
   state.hibpResults keyed by entry id. Concurrency capped at 6 to
   avoid hammering HIBP / hitting browser connection limits.
 - Entry cards show a red "Pwned" chip + breach count in the tooltip
   when count > 0. New i-alert icon added to the SVG sprite.
 - Auto-scan triggered after every enterApp() when the toggle is on.

Functions added to app.js:
 - sha1Hex(text)                       — crypto.subtle wrapper
 - hibpCheckPassword(plaintext)        — single-password check, returns count
 - hibpCheckAllEntries()               — batched scan over state.entries

The "Add-Padding: true" header is sent on every range request to defeat
the response-size side-channel (HIBP adds 800-1000 random extra entries
so an observer counting bytes can't narrow the prefix queried).

CSP tightening
==============
Audited the served HTML: zero <script> tags inline, only the external
js/app.js. Removed 'unsafe-inline' from script-src — real XSS defense.

Kept 'unsafe-inline' on style-src for now because index.html contains
inline style="" attributes and app.js calls element.style.cssText
extensively. Refactoring to CSS classes is a separate cleanup. Style
injection alone cannot execute code, so the residual risk is bounded
to visual manipulation in a single-user loopback app.

Added api.pwnedpasswords.com to connect-src as the only allowed
external origin (required by the HIBP feature above). Default still
'self' — everything else stays loopback.

Before:
  script-src 'self' 'unsafe-inline';
  style-src  'self' 'unsafe-inline';
  connect-src 'self';

After:
  script-src 'self';
  style-src  'self' 'unsafe-inline';
  connect-src 'self' https://api.pwnedpasswords.com;
2026-05-23 05:05:50 +01:00
..