Skip to content

Commit c71eeec

Browse files
authored
Merge pull request #810 from seapagan/fix/timing-attacks
Security: fix timing attacks and token validation vulnerabilities
2 parents 69c9017 + a7c8d10 commit c71eeec

File tree

8 files changed

+538
-40
lines changed

8 files changed

+538
-40
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ repos:
1111
- id: check-added-large-files
1212

1313
- repo: https://github.com/astral-sh/ruff-pre-commit
14-
rev: v0.14.10
14+
rev: v0.14.11
1515
hooks:
1616
- id: ruff
1717
args: ["--output-format=concise"]
@@ -31,7 +31,7 @@ repos:
3131

3232
- repo: https://github.com/astral-sh/uv-pre-commit
3333
# uv version.
34-
rev: 0.9.18
34+
rev: 0.9.22
3535
hooks:
3636
# Update the uv lockfile
3737
- id: uv-lock

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ following advantages to starting your own from scratch :
134134
applied to login (5/15min), registration (3/hour), password recovery (3/hour),
135135
and other auth routes. Returns HTTP 429 with `Retry-After` header when limits
136136
exceeded. Violations tracked via Prometheus metrics when enabled.
137+
- **Timing attack protection** in authentication flows. Login operations use
138+
constant-time password verification to prevent user enumeration, and token
139+
validation uses constant-time comparisons for defense-in-depth.
137140
- **A command-line admin tool**. This allows to configure the project metadata
138141
very easily, add users (and make admin), and run a development server. This
139142
can easily be modified to add your own functionality (for example bulk add

SECURITY-REVIEW.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@
116116

117117
### 6. Timing Attack in Login - User Enumeration
118118

119+
> [!NOTE]
120+
> **Done**: Login now uses a precomputed dummy hash to ensure password
121+
> verification always occurs, maintaining consistent response times regardless
122+
> of user existence.
123+
119124
**Location**: `app/managers/user.py:172-199`
120125

121126
- **Issue**: Different execution paths leak information through timing:
@@ -136,6 +141,12 @@
136141

137142
### 7. Timing Attack in Token Type Checking
138143

144+
> [!NOTE]
145+
> **Done**: Token type comparisons now use `secrets.compare_digest()` for
146+
> constant-time comparison. Note: The timing difference (nanoseconds) is
147+
> negligible compared to network jitter (milliseconds), making this purely
148+
> defense-in-depth rather than addressing a realistic threat vector.
149+
139150
**Location**: `app/managers/auth.py:191, 265, 380`
140151

141152
- **Issue**: String comparison `if payload["typ"] != "refresh"` may be vulnerable
@@ -208,6 +219,12 @@
208219

209220
### 12. KeyError Throws 500 Instead of 401
210221

222+
> [!NOTE]
223+
> **Done**: All JWT claim accesses now use `payload.get()` with explicit
224+
> None checks in all token validation flows (refresh, verify, reset,
225+
> get_jwt_user). Malformed tokens missing 'sub' or 'typ' claims now properly
226+
> return 401 Unauthorized instead of 500 Internal Server Error.
227+
211228
**Location**: `app/managers/auth.py:191, 265, 380`
212229

213230
- **Issue**: `payload["typ"]` / `payload["sub"]` are accessed directly in
@@ -446,6 +463,13 @@
446463

447464
### 30. Timing Attack in API Key Validation
448465

466+
> [!NOTE]
467+
> **Not fixing**: The prefix (`"fta_"`) is public, documented information that
468+
> provides no security value. Timing differences (nanoseconds) are completely
469+
> buried by network jitter. The actual secret (32-byte random key) is compared
470+
> via HMAC hash lookup in the database. No security benefit from constant-time
471+
> prefix comparison.
472+
449473
**Location**: `app/managers/api_key.py:136-137`
450474

451475
- **Issue**: String prefix comparison `if not raw_key.startswith(cls.KEY_PREFIX)`
@@ -568,7 +592,7 @@
568592
| Priority | Count | Must Fix Before Production? |
569593
|--------------|---------------|-------------------------------------|
570594
| **CRITICAL** | 5 (4 closed) | ✅ YES - Security vulnerabilities |
571-
| **High** | 9 (0 closed) | ✅ YES - Important security/quality |
595+
| **High** | 9 (3 closed) | ✅ YES - Important security/quality |
572596
| **Medium** | 14 (0 closed) | ⚠️ Recommended - Hardening needed |
573597
| **Low** | 5 (0 closed) | 💡 Optional - Nice to have |
574598

@@ -600,12 +624,12 @@ rate limiting, token validation, and API key scope enforcement.
600624

601625
### Sprint 2 - High Priority (Next Week)
602626

603-
1. **Fix timing attacks** (#6, #7) - Login and token validation
627+
1. **Fix timing attacks** (#6, #7) - Login and token validation
604628
2. **Implement token revocation** (#8) - Add jti claims + blacklist
605629
3. **Add database index** (#16) - `api_key.user_id`
606630
4. **Refactor token encoding** (#15) - Remove code duplication
607631
5. **Fix password reset reuse** (#9) - One-time tokens
608-
6. **Add KeyError protection** (#12) - Use `payload.get()`
632+
6. **Add KeyError protection** (#12) - Use `payload.get()`
609633
7. **Add JWT format guards** (#13) - to `get_jwt_user()`
610634

611635
### Sprint 3 - Hardening (Next 2 Weeks)

0 commit comments

Comments
 (0)