TASK-031: Enforce bcrypt 72-byte password limit
Bcrypt silently truncates passwords at 72 bytes, so passwords longer than 72 characters provide no additional security. This commit enforces the 72-byte maximum across the authentication and setup flows. Changes: - Add max_length=72 to LoginRequest.password and SetupRequest.master_password - Update field validator in SetupRequest to explicitly check max_length - Add comprehensive tests for password length validation (6 new test cases) - Document the 72-byte limitation in Features.md (master password options) - Add new section 12 'Password Hashing' in Backend-Development.md explaining: - The bcrypt truncation behavior - Why the limit is enforced - The validation flow from frontend to backend - What happens when passwords exceed the limit All existing tests pass, no regressions introduced. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1085,7 +1085,39 @@ The login endpoint (`POST /api/auth/login`) is protected against brute-force att
|
||||
|
||||
---
|
||||
|
||||
## 13. File I/O Conventions
|
||||
## 12. Password Hashing
|
||||
|
||||
The master password is hashed using **bcrypt** with an auto-generated salt. All password validation uses the models in `app.models.auth` and `app.models.setup`.
|
||||
|
||||
### The 72-Byte Bcrypt Limitation
|
||||
|
||||
**Important:** bcrypt silently truncates all input at **72 bytes** before hashing. This means:
|
||||
- A user who sets a 100-character password is actually authenticated by only the first 72 bytes
|
||||
- Extra characters beyond 72 bytes provide **zero additional security**
|
||||
- An attacker who has reduced their search space to 72 bytes can brute-force the password more efficiently than intended
|
||||
|
||||
**Solution:** Both password fields enforce a **maximum length of 72 bytes**:
|
||||
- `LoginRequest.password` — max 72 characters (enforced via Pydantic `Field(max_length=72)`)
|
||||
- `SetupRequest.master_password` — max 72 characters (enforced via Pydantic `Field(max_length=72)`)
|
||||
|
||||
**Validation flow:**
|
||||
1. Frontend → hashes password with SHA256 using `SubtleCrypto` before transmission
|
||||
2. Backend receives SHA256 hash, validates length (≤ 72 bytes)
|
||||
3. Backend → hashes with bcrypt using `run_blocking(bcrypt.hashpw)` to avoid event loop stall
|
||||
4. Hash stored in SQLite `settings` table
|
||||
|
||||
**If a password exceeds 72 bytes:**
|
||||
- Pydantic raises `ValidationError` with error code `string_too_long`
|
||||
- The router returns **HTTP 422 Unprocessable Entity**
|
||||
- The frontend should inform the user to choose a shorter password
|
||||
|
||||
**Implementation:**
|
||||
- Models: `app.models.auth.LoginRequest`, `app.models.setup.SetupRequest`
|
||||
- Service layer: `app.services.auth_service._check_password()`, `app.services.setup_service.run_setup()`
|
||||
|
||||
---
|
||||
|
||||
## 14. File I/O Conventions
|
||||
|
||||
All file write operations to critical configuration files must be **atomic** to prevent corruption if the process is killed mid-write.
|
||||
|
||||
@@ -1163,7 +1195,7 @@ atomic_write(path, updated_content) # Atomic write, auto-cleanup on error
|
||||
|
||||
---
|
||||
|
||||
## 14. Git & Workflow
|
||||
## 15. Git & Workflow
|
||||
|
||||
- **Branch naming:** `feature/<short-description>`, `fix/<short-description>`, `chore/<short-description>`.
|
||||
- **Commit messages:** imperative tense, max 72 chars first line (`Add jail reload endpoint`, `Fix ban history query`).
|
||||
@@ -1173,7 +1205,7 @@ atomic_write(path, updated_content) # Atomic write, auto-cleanup on error
|
||||
|
||||
---
|
||||
|
||||
## 15. Coding Principles
|
||||
## 16. Coding Principles
|
||||
|
||||
These principles are **non-negotiable**. Every backend contributor must internalise and apply them daily.
|
||||
|
||||
@@ -1560,7 +1592,7 @@ When user-supplied URLs are fetched by the backend, validate them before making
|
||||
|
||||
---
|
||||
|
||||
## 16. Quick Reference — Do / Don't
|
||||
## 17. Quick Reference — Do / Don't
|
||||
|
||||
| Do | Don't |
|
||||
|---|---|
|
||||
|
||||
Reference in New Issue
Block a user