V0.1.1 release, close to actual release. Bug & security fixes/improvements.
This commit is contained in:
61
agent1_batch3.md
Normal file
61
agent1_batch3.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Server-Side Password Authentication Flow Assessment (Batch 3)
|
||||
|
||||
**File inspected:** `crates/cgcx-server/src/main.rs`
|
||||
|
||||
## Flow Verification
|
||||
|
||||
### 1. `get_metadata` handler (`/api/content/:cxid`)
|
||||
- **Lines ~496–568**
|
||||
- Sequence:
|
||||
1. Content status check (Deleted/Blacklisted) → **404 NotFound**
|
||||
2. Max-views check (`content.view_count >= max`) → **410 Gone**
|
||||
3. Password validation via `password_from_request(&headers, query.sc.as_deref(), ...)` → **401 Unauthorized** if invalid
|
||||
4. Returns metadata JSON if all checks pass
|
||||
- **View increment:** None. Metadata requests do not consume auto-destroy views.
|
||||
- **Conclusion:** Password check is enforced and returns 401 on failure. ✅
|
||||
|
||||
### 2. `serve_file` handler (`/api/content/:cxid/file/:file_idx`)
|
||||
- **Lines ~700–890**
|
||||
- Sequence:
|
||||
1. Content status check → **404**
|
||||
2. Max-views check → **410**
|
||||
3. Password validation via `password_from_request` → **401** if invalid
|
||||
4. Download permission check → **403 Forbidden** if `?download=true` but not allowed
|
||||
5. File lookup, path-traversal validation, ETag conditional check (304)
|
||||
6. Range header parsing
|
||||
7. **View increment at line ~825:** `repo.increment_views(&content_id).await?`
|
||||
8. If `new_views >= max`, spawns background auto-delete task after 30s
|
||||
9. Streams decrypted file body
|
||||
- **Conclusion:** View increment happens **after** password validation and all other guards. Unauthorized requests cannot consume views. ✅
|
||||
|
||||
### 3. `password_from_request` helper
|
||||
- **Lines ~436–475**
|
||||
- Validates `sc` query parameter using Argon2 (`PasswordHash::new` + `Argon2::default().verify_password`).
|
||||
- Falls back to `cgcx_pw` cookie verified with HMAC-SHA256 and `subtle::ConstantTimeEq`.
|
||||
- Returns `None` on any mismatch, causing the caller to return 401.
|
||||
- **Conclusion:** Argon2 verification is present and constant-time. ✅
|
||||
|
||||
### 4. `serve_raw_file` handler (`/api/content/:cxid/file/:file_idx/raw`)
|
||||
- **Lines ~920–1020**
|
||||
- Sequence mirrors `serve_file` for status, max-views, and password checks.
|
||||
- **View increment:** None. This endpoint never increments `view_count`.
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases & Concerns
|
||||
|
||||
| # | Edge Case | Impact | Risk |
|
||||
|---|-----------|--------|------|
|
||||
| 1 | **`serve_raw_file` skips view increment** | Requests to the `/raw` endpoint do not consume auto-destroy views. A user could repeatedly preview raw text without triggering deletion. | Medium — bypasses view-count enforcement for text content previews. |
|
||||
| 2 | **Zero-size files in `serve_file` return early** | The zero-size branch (line ~751) returns a response **before** the increment block, so zero-byte files never consume a view. | Low — niche, but technically bypasses max-views for empty files. |
|
||||
| 3 | **Range/conditional/HEAD requests skip increment** | `serve_file` only increments when `!is_range && !is_conditional && !is_head`. Video/audio seeking (range requests), cache revalidation (If-None-Match), and HEAD probes do not count as views. | Low — intentional for UX, but means views are under-counted. |
|
||||
| 4 | **Max-views checked before password validation** | In all three handlers, if `view_count >= max`, a request with a wrong password receives **410 Gone** instead of **401 Unauthorized**. This leaks that the content existed and exhausted its views without requiring the password. | Low — information disclosure about content lifecycle. |
|
||||
| 5 | **TOCTOU race on view increment** | `content.view_count` is read in `repo.get()` and incremented later in a separate statement. Under concurrent requests, two clients could both pass the initial `view_count < max` check and both increment, causing `view_count` to exceed `max`. Both requests are still served. | Low — SQLite serializes writes via `conn.lock()`, but the read-write gap still allows one extra view. |
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
- **Password authentication flow is correct.** Wrong passwords receive 401 and do **not** increment view counts.
|
||||
- **No server changes are required for Batch 3.** The frontend password fix (if any) is independent of server behavior.
|
||||
- The identified edge cases are pre-existing behaviors. None are blockers for Batch 3, but items 1 (`serve_raw_file` bypass) and 5 (TOCTOU race) may be worth addressing in a future hardening pass if auto-destroy accuracy is critical.
|
||||
Reference in New Issue
Block a user