feat: reject common passwords in SetupRequest
- Add ~75 common plaintext passwords to setup.py validator - Check case-insensitively; passes complexity but blocked - Add tests: reject common, accept unique, short common fail on length - Update Security.md docs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -88,6 +88,7 @@ See `backend/app/middleware/csrf.py` and `backend/app/middleware/rate_limit.py`
|
|||||||
- Passwords are hashed with SHA256 on the frontend before transmission
|
- Passwords are hashed with SHA256 on the frontend before transmission
|
||||||
- The backend never stores plain-text passwords
|
- The backend never stores plain-text passwords
|
||||||
- See `backend/app/services/auth.py` for authentication implementation
|
- See `backend/app/services/auth.py` for authentication implementation
|
||||||
|
- **Common password prevention:** The setup validator rejects a list of ~75 common plaintext passwords that pass structural complexity checks (e.g., `Password1!`). The list is embedded in `backend/app/models/setup.py` and is checked case-insensitively.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,3 @@
|
|||||||
### Issue #30: LOW-MEDIUM - IPv4-Mapped IPv6 Address Duplicates
|
|
||||||
|
|
||||||
**Where found**:
|
|
||||||
- `backend/app/utils/ip_utils.py`
|
|
||||||
- Treats "192.168.1.1" and "::ffff:192.168.1.1" as different IPs
|
|
||||||
|
|
||||||
**Why this is needed**:
|
|
||||||
Same IP can be banned twice in different formats, causing:
|
|
||||||
- Duplicate ban logs
|
|
||||||
- Geo cache duplicates
|
|
||||||
- Analytics skewed
|
|
||||||
|
|
||||||
**Goal**:
|
|
||||||
Normalize IP addresses to canonical form.
|
|
||||||
|
|
||||||
**What to do**:
|
|
||||||
1. Add normalization:
|
|
||||||
```python
|
|
||||||
def normalize_ip(ip_str: str) -> str:
|
|
||||||
ip = ipaddress.ip_address(ip_str)
|
|
||||||
# Convert IPv4-mapped IPv6 to IPv4
|
|
||||||
if isinstance(ip, ipaddress.IPv6Address) and ip.ipv4_mapped:
|
|
||||||
return str(ip.ipv4_mapped)
|
|
||||||
return str(ip)
|
|
||||||
```
|
|
||||||
2. Apply on all IP inputs (ban, import, etc.)
|
|
||||||
3. Test with various formats
|
|
||||||
|
|
||||||
**Docs changes needed**:
|
|
||||||
- Document IP normalization
|
|
||||||
|
|
||||||
**Doc references**:
|
|
||||||
- DETAILED_FINDINGS.md - Issue #22 "IPv4-Mapped IPv6"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Issue #31: LOW-MEDIUM - Weak Master Password Validation
|
### Issue #31: LOW-MEDIUM - Weak Master Password Validation
|
||||||
|
|
||||||
**Where found**:
|
**Where found**:
|
||||||
|
|||||||
@@ -191,6 +191,35 @@ def test_setup_request_master_password_complexity_still_enforced() -> None:
|
|||||||
assert "special character" in str(exc_info.value)
|
assert "special character" in str(exc_info.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_setup_request_rejects_common_passwords() -> None:
|
||||||
|
"""SetupRequest rejects common passwords that pass all other complexity checks."""
|
||||||
|
from app.models.setup import SetupRequest
|
||||||
|
|
||||||
|
# Passw0rd! passes length (10), uppercase, digit, special checks but is too common
|
||||||
|
with pytest.raises(ValidationError) as exc_info:
|
||||||
|
SetupRequest(master_password="Passw0rd!")
|
||||||
|
assert "too common" in str(exc_info.value).lower()
|
||||||
|
|
||||||
|
|
||||||
|
def test_setup_request_accepts_valid_unique_password() -> None:
|
||||||
|
"""SetupRequest accepts a password that meets all requirements and is not common."""
|
||||||
|
from app.models.setup import SetupRequest
|
||||||
|
|
||||||
|
req = SetupRequest(master_password="MyV3ryStr0ng!P@ssw0rd")
|
||||||
|
assert req.master_password == "MyV3ryStr0ng!P@ssw0rd"
|
||||||
|
|
||||||
|
|
||||||
|
def test_setup_request_rejects_short_common_passwords() -> None:
|
||||||
|
"""SetupRequest rejects short common passwords (rejected for length, not common check)."""
|
||||||
|
from app.models.setup import SetupRequest
|
||||||
|
|
||||||
|
for password in ["letmein", "admin", "qwerty", "shadow"]:
|
||||||
|
with pytest.raises(ValidationError) as exc_info:
|
||||||
|
SetupRequest(master_password=password)
|
||||||
|
# These fail the minimum length check first
|
||||||
|
assert "at least 8 characters" in str(exc_info.value)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# DashboardBanItem country_code validator
|
# DashboardBanItem country_code validator
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user