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;
This commit is contained in:
@@ -138,9 +138,22 @@ var
|
||||
begin
|
||||
AResponse.CustomHeaders.Values['Strict-Transport-Security'] :=
|
||||
'max-age=31536000; includeSubDomains';
|
||||
// Content-Security-Policy — tightened May 2026:
|
||||
// - script-src: removed 'unsafe-inline'. No <script> tags inline in the
|
||||
// served HTML — only external js/app.js. Real XSS defense.
|
||||
// - style-src: KEPT 'unsafe-inline' because index.html has inline
|
||||
// style="" attributes and app.js sets element.style.cssText
|
||||
// extensively. Refactoring that to use CSS classes would be a
|
||||
// separate cleanup pass. Style injection alone cannot execute code,
|
||||
// so the risk is bounded to visual manipulation / data exfil via
|
||||
// CSS selectors (low impact in a single-user loopback app).
|
||||
// - connect-src: 'self' + api.pwnedpasswords.com to allow the HIBP
|
||||
// range API. Only the SHA-1[0..5] prefix ever leaves the machine.
|
||||
AResponse.CustomHeaders.Values['Content-Security-Policy'] :=
|
||||
'default-src ''self''; script-src ''self'' ''unsafe-inline''; ' +
|
||||
'style-src ''self'' ''unsafe-inline''; connect-src ''self''; ' +
|
||||
'default-src ''self''; ' +
|
||||
'script-src ''self''; ' +
|
||||
'style-src ''self'' ''unsafe-inline''; ' +
|
||||
'connect-src ''self'' https://api.pwnedpasswords.com; ' +
|
||||
'img-src ''self'' data:; font-src ''self''; form-action ''self''; ' +
|
||||
'frame-ancestors ''none''; base-uri ''self''; object-src ''none''';
|
||||
AResponse.CustomHeaders.Values['X-Content-Type-Options'] := 'nosniff';
|
||||
|
||||
Reference in New Issue
Block a user