Aug 26 - Enhance security 3
CI / Python lint (flake8) (push) Has been cancelled
CI / Python syntax check (push) Has been cancelled
CI / Alembic migration chain (push) Has been cancelled
CI / JavaScript syntax check (push) Has been cancelled
CI / Pytest (push) Has been cancelled
CI / Build extension zip (push) Has been cancelled

This commit is contained in:
2026-08-26 13:54:48 -04:00
parent 6c1bef73c8
commit cc216b0d98
16 changed files with 11056 additions and 43 deletions
+22 -2
View File
@@ -82,6 +82,7 @@ passkeeper/
│ ├── background.firefox.js # Firefox: in-memory session shim, setTimeout idle lock
│ ├── shared/
│ │ ├── crypto.js # PBKDF2+AES-GCM; encryptName/decryptName; extractable key
│ │ ├── psl.js # GENERATED — vendored Public Suffix List + PkPsl.isSameSite
│ │ └── browser-polyfill.js # chrome=browser alias for Firefox content scripts
│ ├── popup/
│ │ ├── popup.html # Tabs: All relevant / All items / Favorites / Recents
@@ -106,6 +107,7 @@ passkeeper/
│ ├── g7h8i9j0k1l2_encrypt_shared_item_name.py # enc_name/iv_name on shared_items
│ └── h8i9j0k1l2m3_add_webauthn_credentials_table.py # Passkey / WebAuthn credentials
├── scripts/
│ ├── update_psl.py # regenerates extension/shared/psl.js
│ ├── reencrypt_totp_secrets.py
│ ├── backup_db.sh / backup.cron / passkeeper-logrotate
│ ├── passkeeper-nginx.conf / passkeeper.service
@@ -115,7 +117,9 @@ passkeeper/
│ ├── test_key_rotation.py # re-encryption completeness guard
│ ├── test_session_revocation.py # token_epoch revocation; deleted-account 401
│ ├── test_webauthn_uv.py # user verification required on both ceremonies
── test_deploy_config.py # nginx/gunicorn/systemd invariants (502 guards)
── test_sharing_expiry.py # expires_days fails closed
│ ├── test_deploy_config.py # nginx/gunicorn/systemd/extension packaging guards
│ └── js/test_psl.js # PSL same-site matching (node, run in CI)
├── gunicorn.conf.py # worker class, timeouts, preload_app=False
├── pytest.ini
├── requirements-dev.txt
@@ -264,6 +268,15 @@ CREATE TABLE webauthn_credentials (
- **MFA:** TOTP secret AES-256-GCM encrypted at rest; each code is single-use (replay prevented via `totp_used_codes` table, 120s TTL)
- **Passkeys / WebAuthn:** server authentication via FIDO2; ZK model preserved — WebAuthn proves identity to the server but the vault key is still derived from the master password client-side; `sign_count` updated on every assertion for clone detection
- **Sharing:** ECDH P-256 zero-knowledge re-encryption; item name also encrypted with shared key
- **Autofill same-site rule:** `PkPsl.isSameSite()` compares *registrable
domains* using the vendored Public Suffix List — never suffix comparison.
`host.endsWith("." + h)` treated `evil.github.io` and `victim.github.io` as
the same site and let an item saved for a bare TLD match everything under it.
Used identically by `content.js`, `popup.js`, `background.js` and
`background.firefox.js`; all four fall back to exact hostname equality if
`psl.js` fails to load (strict, so a failure loses matches rather than
leaking credentials). The PRIVATE section of the list is required — that is
where `github.io` / `vercel.app` / `herokuapp.com` live.
- **Password generator:** fully CSPRNG (`_cryptoRandInt` rejection-sampling)
- **Decrypted vault data:** `chrome.storage.session` only — never to disk
- **Clipboard auto-clear:** 30 s after any password/username copy (web + extension)
@@ -630,6 +643,9 @@ Audit log details **never** contain plaintext item names, shared item names, or
- WebAuthn `attachment`: `"cross-platform"` for security keys; `"platform"` for device biometrics (default)
- `enc_vault_is_legacy` check in `EmergencyAccess.to_dict()` is pure JSON inspection — no decryption
- Never return `str(e)` from exception handlers — log with `_log.exception(...)` and return a generic user-facing message to avoid leaking DB schema details or query fragments
- `extension/shared/psl.js` is GENERATED — never hand-edit; run `python scripts/update_psl.py`. It must load BEFORE content.js / popup.js / background.js in every manifest
- Never reintroduce `endsWith("." + host)` host matching anywhere in the extension — `tests/test_deploy_config.py` fails the build if it reappears
- nginx rate zones: mind `r/s` vs `r/m`. `api_limit` was `60r/m` (1 req/s for the whole API) and caused spurious 429s on normal vault use
- `preload_app` must stay `False` in `gunicorn.conf.py` — APScheduler's thread does not survive `fork()`, so `--preload` silently disables the cleanup job
- nginx `proxy_read_timeout` must stay BELOW gunicorn `timeout`, else a slow request returns 502 instead of 504
- `WatchdogSec` in the systemd unit requires `Type=notify` + `NotifyAccess=main`; without them systemd SIGKILLs the service on a loop
@@ -838,7 +854,11 @@ Features planned for future implementation. Ordered by priority within each cate
- Key-rotation completeness guard on `change_password` / `/recover`
- Session revocation via `token_epoch`; `require_jwt` now verifies the user exists
- Passkey ceremonies require user verification
- pytest suite (37 tests) + CI job; `gunicorn.conf.py`; systemd watchdog removed
- Autofill matching moved onto the Public Suffix List (registrable domains)
- Share `expires_days` fails closed instead of silently meaning "never"
- nginx `api_limit` corrected from 60r/m to 10r/s
- pytest suite (51 tests) + PSL node test + CI jobs; `gunicorn.conf.py`;
systemd watchdog removed
### High priority — user-facing