Aug 26 - Update password detect against off field 2
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
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:
@@ -53,6 +53,7 @@ passkeeper/
|
||||
│ │ ├── emergency_access.py # State machine
|
||||
│ │ ├── recovery_challenge.py # Server-side recovery challenge (multi-worker safe)
|
||||
│ │ ├── webauthn_credential.py # Passkey / WebAuthn credentials (one row per key)
|
||||
│ │ ├── login_attempt.py # Failed-login lockout scoped to (user, IP)
|
||||
│ │ └── audit_log.py
|
||||
│ ├── routes/
|
||||
│ │ ├── auth.py # Register, login, MFA, logout, refresh, change-password, recovery
|
||||
@@ -120,6 +121,7 @@ passkeeper/
|
||||
│ ├── test_sharing_expiry.py # expires_days fails closed
|
||||
│ ├── test_registration_privacy.py # register does not disclose account existence
|
||||
│ ├── test_emergency_visibility.py # grantor sees requests + retrievals
|
||||
│ ├── test_login_lockout.py # per-IP lockout; no disclosure, no DoS
|
||||
│ ├── test_deploy_config.py # nginx/gunicorn/systemd/extension packaging guards
|
||||
│ └── js/
|
||||
│ ├── test_psl.js # PSL same-site matching (node, run in CI)
|
||||
@@ -249,6 +251,7 @@ CREATE TABLE webauthn_credentials (
|
||||
| `j0k1l2m3n4o5` | Add recovery_verifier (decouple recovery proof) |
|
||||
| `k1l2m3n4o5p6` | Add token_epoch (revoke sessions on pw change) |
|
||||
| `l2m3n4o5p6q7` | Add emergency vault retrieval tracking |
|
||||
| `m3n4o5p6q7r8` | Add login_attempts (per-IP lockout) |
|
||||
|
||||
---
|
||||
|
||||
@@ -261,6 +264,13 @@ CREATE TABLE webauthn_credentials (
|
||||
- **Item name:** `enc_name`/`iv_name` in `vault_items`; server `name` column = item type only
|
||||
- **Shared item name:** `enc_name`/`iv_name` encrypted with ECDH shared key; server `item_name` = item type only
|
||||
- **Tags:** `plain.tags: string[]` inside `enc_data`; server never sees them
|
||||
- **Login lockout:** scoped to (account, source IP) in `login_attempts`, NOT
|
||||
global. A global counter made it a DoS primitive — anyone knowing an address
|
||||
could lock the real owner out for 15 minutes, repeatedly. Every failure mode
|
||||
(unknown account / wrong password / locked out) returns one identical 401 with
|
||||
matching timing, so it discloses nothing. `users.failed_login_count` and
|
||||
`locked_until` remain as an aggregate audit signal only; they no longer gate
|
||||
authentication.
|
||||
- **Argon2id:** double-hashes `authHash` server-side; transparently rehashes on login if parameters are upgraded
|
||||
- **JWT:** HS256, 15 min access / 7 day refresh, JTI blacklisted on logout.
|
||||
Every token carries an `epoch` claim checked against `users.token_epoch`;
|
||||
@@ -647,6 +657,35 @@ is ever dispatched and the save-credentials banner never appeared.
|
||||
|
||||
`_captureCooldown` (2 s) prevents two triggers double-prompting for one login.
|
||||
|
||||
### Insecure-page warning
|
||||
|
||||
The extension keeps `http://*/*` permission deliberately: routers, NAS boxes,
|
||||
printers and self-hosted panels are often reachable only over plain HTTP on the
|
||||
LAN, and those are exactly the devices whose passwords get reused.
|
||||
|
||||
`_isTrustworthyOrigin()` classifies the page. HTTPS, `file:`, `localhost`,
|
||||
reserved TLDs (`.local` / `.lan` / `.home` / `.internal`) and RFC1918 /
|
||||
loopback / link-local / RFC4193 addresses are accepted silently. Any other
|
||||
`http://` origin gets a red warning row prepended to the suggestion dropdown.
|
||||
|
||||
**IPv4 checks must match in FULL** (`$`-anchored). A prefix test like
|
||||
`hostname.startsWith("127.")` also accepts the registrable
|
||||
`127.0.0.1.evil.com`, which would silently suppress the warning on a hostile
|
||||
site. Guarded by `tests/js/test_field_heuristics.js`.
|
||||
|
||||
Filling is never automatic, so this warns rather than blocks — silently
|
||||
offering nothing would just look like a broken extension.
|
||||
|
||||
### Autologin without a `<form>`
|
||||
|
||||
`_findSubmitControl(pwField)` locates the control that submits the login,
|
||||
walking up to 5 ancestors when there is no `<form>`. It skips invisible
|
||||
elements, wrappers containing other inputs, labels over 40 chars, and anything
|
||||
matching `_NEGATIVE_CONTROL` (cancel / reset / back / forgot / register / sign
|
||||
up), then clicks it — `form.submit()` is only the last resort because it
|
||||
bypasses site handlers entirely. Returns null when nothing is convincing;
|
||||
leaving a filled form for the user beats clicking the wrong thing.
|
||||
|
||||
### MutationObserver guard
|
||||
|
||||
Inspects added/removed nodes — if all carry `__pk` prefix, returns early. Prevents re-decoration loops when the extension injects/removes its own UI.
|
||||
@@ -682,7 +721,7 @@ Audit log details **never** contain plaintext item names, shared item names, or
|
||||
- `#vault-list` ID must not be renamed — `vault.js` renders into it directly
|
||||
- `_validate_folder_id` must be called for any user-supplied `folder_id` before DB write
|
||||
- `verify_auth_token` must receive `user=user` at both `login` and `change_password` to enable Argon2 rehash
|
||||
- APScheduler cleanup job handles `TokenBlacklist`, `RecoveryChallenge`, AND `TotpUsedCode`; guard with `os.environ.get('WERKZEUG_RUN_MAIN') == 'true'` in Flask debug mode to prevent double-start
|
||||
- APScheduler cleanup job handles `TokenBlacklist`, `RecoveryChallenge`, `TotpUsedCode`, expired shares AND `LoginAttempt`; guard with `os.environ.get('WERKZEUG_RUN_MAIN') == 'true'` in Flask debug mode to prevent double-start
|
||||
- `password_changed_at` lives inside `plain` (encrypted) — never in the server schema
|
||||
- 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
|
||||
@@ -690,6 +729,9 @@ Audit log details **never** contain plaintext item names, shared item names, or
|
||||
- `/register` must return the SAME body and status for new and existing addresses, and hash on both paths — returning early on duplicate reinstates a timing oracle
|
||||
- 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
|
||||
- Host/IP allowlists must be `$`-anchored — `startsWith("127.")` also matches the attacker-registrable `127.0.0.1.evil.com`
|
||||
- `escHtml` must escape `&`, `<`, `>`, `"` AND `'` in all three copies (vault.js, popup.js, content.js) — templates mix quote styles
|
||||
- Login lockout lives in `login_attempts` keyed by (user, IP); never move it back to a global per-account counter
|
||||
- Never add `off` back to `NON_CRED_AC` in `content.js` — `tests/js/test_field_heuristics.js` fails the build if you do
|
||||
- Login detection must not assume a `<form>` exists; route new capture triggers through `maybeCaptureCredentials()`
|
||||
- Never reintroduce `endsWith("." + host)` host matching anywhere in the extension — `tests/test_deploy_config.py` fails the build if it reappears
|
||||
@@ -711,7 +753,7 @@ Audit log details **never** contain plaintext item names, shared item names, or
|
||||
| `auth.py` | `auth.register` | New account |
|
||||
| `auth.py` | `auth.register_duplicate` | Register attempt on an existing address (no email in detail) |
|
||||
| `auth.py` | `auth.login` / `auth.login_failed` | Login success/fail |
|
||||
| `auth.py` | `auth.account_locked` | Failed login lockout |
|
||||
| `auth.py` | `auth.account_locked` / `auth.login_blocked` | Per-IP lockout triggered / hit |
|
||||
| `auth.py` | `auth.mfa_enable/disable/verify` | TOTP actions |
|
||||
| `auth.py` | `auth.mfa_backup_code_used` | Backup code login |
|
||||
| `auth.py` | `auth.mfa_backup_codes_regenerated` | Backup code regen |
|
||||
@@ -908,8 +950,12 @@ Features planned for future implementation. Ordered by priority within each cate
|
||||
- Share `expires_days` fails closed instead of silently meaning "never"
|
||||
- nginx `api_limit` corrected from 60r/m to 10r/s
|
||||
- Registration no longer discloses account existence (status, body and timing)
|
||||
- Login lockout scoped per-IP: no account disclosure, no lock-out-the-owner DoS
|
||||
- `escHtml` escapes single quotes; dead `User.check_password` removed
|
||||
- Extension warns before filling on plaintext public pages; autologin works
|
||||
without a `<form>`
|
||||
- Emergency access: requests and vault retrievals are now visible to the grantor
|
||||
- pytest suite (66 tests) + PSL node test + CI jobs; `gunicorn.conf.py`;
|
||||
- pytest suite (77 tests) + 2 node test files + CI jobs; `gunicorn.conf.py`;
|
||||
systemd watchdog removed
|
||||
|
||||
### High priority — user-facing
|
||||
|
||||
Reference in New Issue
Block a user