| 1 |
1 |
|
# MakeNotWork -- Audit Review
|
| 2 |
2 |
|
|
| 3 |
|
- |
**Last audited:** 2026-05-09 (Run 24, Ultra Fuzz -- 5-axis deep audit)
|
| 4 |
|
- |
**Previous audit:** 2026-05-09 (Run 23, Ultra Fuzz -- 5-axis deep audit)
|
|
3 |
+ |
**Last audited:** 2026-05-11 (Run 25, Ultra Fuzz -- 5-axis deep audit)
|
|
4 |
+ |
**Previous audit:** 2026-05-09 (Run 24, Ultra Fuzz -- 5-axis deep audit)
|
| 5 |
5 |
|
|
| 6 |
6 |
|
## Overall Grade: A
|
| 7 |
7 |
|
|
| 8 |
|
- |
Run 24: Ultra Fuzz (Payments, Storage, UX Wiring, Security, Performance). v0.5.9. ~88,082 LOC. ~1,215 test annotations. 107 migrations. 2 SERIOUS + 3 MINOR findings identified, all fixed in same-day remediation (1 false positive). 0 cold spots (all 2 resolved). 5/5 axes at A. All Run 23 fixes verified intact. No regressions.
|
|
8 |
+ |
Run 25: Ultra Fuzz (Payments, Storage, UX Wiring, Security, Performance). v0.5.14. ~88,978 LOC. ~1,225 test annotations. 111 migrations. 3 SERIOUS + 6 MINOR findings identified. 5 cold spots. 5/5 axes at A. All Run 24 fixes verified intact. No regressions.
|
| 9 |
9 |
|
|
| 10 |
10 |
|
## Scorecard
|
| 11 |
11 |
|
|
| 13 |
13 |
|
|-----------|:-----:|-------|
|
| 14 |
14 |
|
| Code Quality | A | Zero .unwrap() in production paths. Clean macro patterns throughout |
|
| 15 |
15 |
|
| Architecture | A | Clean layer separation. Trait-based backends for storage/email/payments |
|
| 16 |
|
- |
| Testing | A | ~1,215 test annotations, proptest active, adversarial tests, comprehensive harness |
|
|
16 |
+ |
| Testing | A | ~1,225 test annotations, proptest active, adversarial tests, comprehensive harness |
|
| 17 |
17 |
|
| Security | A | Constant-time compare, fail-closed scanning, CSRF everywhere, Argon2id, HMAC webhooks, PKCE S256. DUMMY_HASH pattern on all login paths |
|
| 18 |
|
- |
| Performance | A | Cart and download queries consolidated. Export writes to ZIP one-by-one. Advisory lock pinned |
|
|
18 |
+ |
| Performance | A | Discover facets parallelized. Batch loading. Bounded scan semaphore. Advisory lock pinned |
|
| 19 |
19 |
|
| Documentation | A | Module-level //! on all major files. README.md present |
|
| 20 |
20 |
|
| Dependencies | A- | 4 transitive advisories (none exploitable). async-trait retained (required for dyn dispatch) |
|
| 21 |
21 |
|
| Frontend | A | Askama auto-escape, json_escape prevents JSON-LD XSS, HTMX patterns consistent. CSRF field names consistent |
|
| 24 |
24 |
|
| Concurrency | A | ON CONFLICT, FOR UPDATE, DashMap caches. Scheduler advisory lock pinned. Promo code atomically reserved at checkout |
|
| 25 |
25 |
|
| Resilience | A | Graceful shutdown with 10s deadline, timeouts on all outbound calls, fail-closed scanning |
|
| 26 |
26 |
|
| API Consistency | A | ListResponse wrapper, json_error_layer, versioned SyncKit routes |
|
| 27 |
|
- |
| Migration Safety | A | 107 additive migrations, IF NOT EXISTS guards, TIMESTAMPTZ throughout |
|
|
27 |
+ |
| Migration Safety | A | 111 additive migrations, IF NOT EXISTS guards, TIMESTAMPTZ throughout |
|
| 28 |
28 |
|
| Codebase Size | A | All three oversized files split into modules. health/mod.rs at 749 (monolithic probe fn) |
|
| 29 |
29 |
|
|
| 30 |
30 |
|
## Module Heatmap
|
| 42 |
42 |
|
| storage.rs | A | A | A | A | A | A | A+ | B+ | A |
|
| 43 |
43 |
|
| pricing.rs | A | A+ | A+ | A | A | A | A- | n/a | A |
|
| 44 |
44 |
|
| crypto.rs | A | A | A | A+ | A | A | A | n/a | A |
|
| 45 |
|
- |
| formatting.rs | A | A | A+ | A | A | A- | A | n/a | A |
|
|
45 |
+ |
| formatting.rs | A- | A | A+ | A | A | A- | A | n/a | A |
|
| 46 |
46 |
|
| rss.rs | A | A | A | A | A | A | A- | n/a | A |
|
| 47 |
47 |
|
| synckit_auth.rs | A | A | A | A | A | A | A | A | A |
|
| 48 |
48 |
|
| metrics.rs | A | A | n/a | A | A- | A | A | A | A |
|
| 49 |
49 |
|
| db/enums.rs | A | A | A | A | n/a | A | A+ | n/a | A |
|
| 50 |
|
- |
| db/id_types.rs | A | A | A | n/a | n/a | A | A+ | n/a | A |
|
|
50 |
+ |
| db/id_types.rs | A+ | A | A | n/a | n/a | A | A+ | n/a | A+ |
|
| 51 |
51 |
|
| db/validated_types.rs | A | A | A+ | A | A | A | A+ | n/a | A |
|
| 52 |
52 |
|
| db/users.rs | A | A | B | A | A- | A | A | A | A |
|
| 53 |
53 |
|
| db/items.rs | A | A | B | A | A- | A | A | A | A |
|
| 54 |
54 |
|
| db/synckit.rs | A | A | B | A | A | A | A | A | A |
|
| 55 |
|
- |
| db/transactions.rs | A | A | B+ | A | A | A | A | A | A |
|
|
55 |
+ |
| db/transactions.rs | A | A | B+ | A | A | A | A- | A | A |
|
| 56 |
56 |
|
| db/discover.rs | A | A | B | A | A | A | A | n/a | A- |
|
| 57 |
|
- |
| db/cart.rs | A | A | B | A | A | A | A | n/a | A |
|
|
57 |
+ |
| db/cart.rs | **B+** | A | B | A | A | A | A | n/a | A |
|
| 58 |
58 |
|
| db/creator_tiers.rs | A | A+ | A | A | A | A | A | n/a | A |
|
| 59 |
59 |
|
| db/versions.rs | A | A | A | A | A | A | A | n/a | A |
|
| 60 |
60 |
|
| db/builds.rs | A | A+ | A | A | A | A | A | n/a | A |
|
| 61 |
|
- |
| db/pending_refunds.rs | A | A | B | A | A | A | A | n/a | A |
|
|
61 |
+ |
| db/pending_refunds.rs | A+ | A | B | A | A | A | A | n/a | A |
|
|
62 |
+ |
| db/pending_s3_deletions.rs | A | A- | n/a | A | A | A | A | n/a | A |
|
| 62 |
63 |
|
| db/license_keys.rs | A | A | B | A | A | A | A | n/a | A |
|
| 63 |
64 |
|
| db/promo_codes.rs | A | A | A | A | A | A | A | n/a | A |
|
| 64 |
65 |
|
| db/tips.rs | A | A | B | A | A | A | A | n/a | A |
|
| 65 |
66 |
|
| db/idempotency.rs | A | A | B- | A | A | A | A | n/a | A |
|
| 66 |
|
- |
| db/pending_uploads.rs | B+ | A | n/a | A | A | A | A | n/a | A |
|
|
67 |
+ |
| db/pending_uploads.rs | A | A | n/a | A | A | A | A | n/a | A |
|
|
68 |
+ |
| db/moderation.rs | A | A | B+ | A | n/a | A | A | A | A |
|
|
69 |
+ |
| db/reports.rs | A | A | B+ | A | n/a | A | A | A | A |
|
| 67 |
70 |
|
| db/models/* | A | A | B+ | A | n/a | A- | A | n/a | A |
|
| 68 |
71 |
|
| types/ | A | A | B | A | n/a | A | A | n/a | A |
|
| 69 |
72 |
|
| scanning/ | A | A+ | A- | A+ | A- | A | A- | A | A |
|
| 70 |
|
- |
| scanning/archive.rs | A | A | A- | A- | A- | A | A- | A | A |
|
|
73 |
+ |
| scanning/archive.rs | A- | A | A- | A- | A- | A | A- | A | A |
|
|
74 |
+ |
| scanning/structural.rs | A- | A | A | B+ | A | A | A | A- | A |
|
| 71 |
75 |
|
| payments/ | A | A | A- | A | A- | A | A | A | B+ |
|
|
76 |
+ |
| payments/webhooks.rs | A | A | A | A | A | A | A | A | A |
|
|
77 |
+ |
| payments/checkout.rs | A- | A | A- | A | A | A | A- | A | A |
|
| 72 |
78 |
|
| email/ | A | A | A | A | A- | A | A | B+ | A- |
|
| 73 |
79 |
|
| scheduler/ | A | A+ | B+ | A | A | A | A | A- | A |
|
| 74 |
80 |
|
| scheduler/cleanup.rs | A | A | n/a | A | A | A | A | A- | A |
|
| 79 |
85 |
|
| build_runner.rs | A | A- | B+ | A+ | A- | A- | A | A | A |
|
| 80 |
86 |
|
| monitor.rs | A | A | A- | A | A | A | A | A+ | A |
|
| 81 |
87 |
|
| templates/ | A | A- | n/a | A | A | A- | A | B+ | B+ |
|
|
88 |
+ |
| templates/purchase.html | A- | n/a | n/a | A | n/a | n/a | n/a | n/a | **B+** |
|
| 82 |
89 |
|
| routes/auth.rs | A | A | n/a | A+ | A | A | A | A | A |
|
| 83 |
|
- |
| routes/oauth.rs | A | A | n/a | A- | A | A | A | A- | A- |
|
|
90 |
+ |
| routes/oauth.rs | A | A | n/a | A | A | A | A | A- | A- |
|
| 84 |
91 |
|
| routes/admin/ | A | A | n/a | A | A | A | A | A | A |
|
| 85 |
92 |
|
| routes/api/ | A | A | n/a | A | A | A | A | A | B+ |
|
| 86 |
|
- |
| routes/api/guest_checkout.rs | **B** | A | n/a | **B** | A | A | A | A | A |
|
| 87 |
|
- |
| routes/api/cart.rs | A | A | n/a | A | A- | A | A | A | A |
|
|
93 |
+ |
| routes/api/projects.rs (delete) | **B-** | A | n/a | A | A | A | A | A | A |
|
| 88 |
94 |
|
| routes/api/exports/ | A- | A- | n/a | A | A- | A | A | A | A- |
|
|
95 |
+ |
| routes/api/exports/content.rs | **B+** | A- | n/a | A | **B+** | A | A | A | A |
|
| 89 |
96 |
|
| routes/stripe/ | A | A | n/a | A | A- | A | A | A | B+ |
|
| 90 |
97 |
|
| routes/stripe/checkout/ | A | A | n/a | A- | A | A | A | A | A |
|
| 91 |
98 |
|
| routes/stripe/checkout/cart.rs | A | A | n/a | A- | A | A | A | A | A |
|
| 95 |
102 |
|
| routes/embed/ | A | A | n/a | A | A | A- | A | A | A |
|
| 96 |
103 |
|
| routes/git/ | A | A | n/a | A | A | A | A | A | A |
|
| 97 |
104 |
|
| routes/storage/ | A | A | n/a | A | A | A | A | A | A |
|
| 98 |
|
- |
| routes/storage/versions.rs | **B-** | A- | n/a | A | A | A | A | A | A |
|
|
105 |
+ |
| routes/storage/versions.rs | A | A- | n/a | A | A | A | A | A | A |
|
| 99 |
106 |
|
| routes/storage/uploads.rs | A | A | n/a | A | A | A | A | A | A |
|
| 100 |
107 |
|
| routes/storage/downloads.rs | A | A | n/a | A | A- | A | A | A | A |
|
| 101 |
|
- |
| routes/storage/images.rs | A | A | n/a | A | A | A | A | A | A |
|
| 102 |
|
- |
| routes/postmark/ | A | A | n/a | A | A | A | n/a | A- | B+ |
|
|
108 |
+ |
| routes/storage/images.rs | **B+** | A | n/a | A | A | A | A | A | A- |
|
| 103 |
109 |
|
|
| 104 |
110 |
|
**Bold** = cold spot (B or below).
|
| 105 |
111 |
|
|
| 106 |
112 |
|
### Cold Spots
|
| 107 |
113 |
|
|
| 108 |
|
- |
None. All Run 24 cold spots resolved in remediation.
|
|
114 |
+ |
| Module | Grade | Issue |
|
|
115 |
+ |
|--------|:-----:|-------|
|
|
116 |
+ |
| db/cart.rs | B+ | SQL references `t.user_id` instead of `t.buyer_id` — runtime error |
|
|
117 |
+ |
| routes/api/projects.rs (delete) | B- | Project deletion orphans S3 objects, no storage decrement |
|
|
118 |
+ |
| routes/api/exports/content.rs | B+ | Buffers up to 2GB ZIP in memory per export request |
|
|
119 |
+ |
| routes/storage/images.rs | B+ | Non-atomic 3-UPDATE cover image, non-atomic storage swap on project images, missing S3 cleanup |
|
|
120 |
+ |
| templates/purchase.html | B+ | Cart add-to-cart JS doesn't check HTTP error status |
|
| 109 |
121 |
|
|
| 110 |
122 |
|
### Resolved Cold Spots (from Run 24)
|
| 111 |
123 |
|
|
| 123 |
135 |
|
|
| 124 |
136 |
|
## Mandatory Surprises
|
| 125 |
137 |
|
|
| 126 |
|
- |
**Run 24 (5 surprises, one per axis):**
|
|
138 |
+ |
**Run 25 (5 surprises, one per axis):**
|
| 127 |
139 |
|
|
| 128 |
|
- |
1. **Payments -- Claim token architecture for guest purchases (unexpectedly good):** `db/transactions.rs:74-173` creates unattached transactions with unique `claim_token`, sends secure download link, claims idempotently via `ON CONFLICT DO NOTHING`. Cryptographically sound, race-free, elegant.
|
|
140 |
+ |
1. **Payments -- Pending refund queue with FOR UPDATE SKIP LOCKED (unexpectedly good):** `db/pending_refunds.rs` + `checkout_helpers.rs:check_pending_refund`. When a `charge.refunded` webhook arrives before `checkout.session.completed`, the refund is queued and processed after checkout completion. Claims use `FOR UPDATE SKIP LOCKED` for safe concurrency. Stale refunds are escalated. This solves one of the hardest webhook ordering problems correctly.
|
| 129 |
141 |
|
|
| 130 |
|
- |
2. **Storage -- recalculate_all_storage_batch LATERAL join (unexpectedly good):** `db/creator_tiers.rs:458-508` recalculates storage for ALL creators in a single query using LATERAL join instead of N+1 loop. Shows architectural awareness.
|
|
142 |
+ |
2. **Storage -- Durable S3 deletion queue as transactional outbox (unexpectedly good):** `db/pending_s3_deletions.rs` + `scheduler/cleanup.rs:365-418`. Every destructive path enqueues S3 keys to a durable DB table before deletion. Retry with attempt counting, `FOR UPDATE SKIP LOCKED`, and prefix vs single-key distinction. Production-grade pattern -- the one exception (project deletion, see SERIOUS finding) makes its absence there even more glaring.
|
| 131 |
143 |
|
|
| 132 |
|
- |
3. **UX Wiring -- CSRF constant-time token comparison (unexpectedly good):** `csrf.rs:61` uses `constant_time_compare()` for CSRF token validation. Most web frameworks skip this detail. Combined with dual-layer extraction (header + body), this is production-hardened CSRF.
|
|
144 |
+ |
3. **UX Wiring -- json_escape with HTML entity protection (unexpectedly good):** `types/mod.rs:214`. Hand-rolled escaper goes beyond standard JSON to also escape `<`, `>`, `&` as Unicode escapes for safe embedding in `<script>` tags via `|safe`. Combined with `build_segments_json`'s `</` to `<\/` replacement. Defense-in-depth approach most codebases skip entirely.
|
| 133 |
145 |
|
|
| 134 |
|
- |
4. **Security -- TOTP replay prevention via explicit time step matching (unexpectedly good):** `routes/api/totp.rs:254-266` stores matched time step in DB, rejects monotonically non-increasing steps. Completely eliminates TOTP replay across skew window. Most platforms get this wrong.
|
|
146 |
+ |
4. **Security -- Anti-timing dummy hash on ALL login paths (unexpectedly good):** `routes/auth.rs:29-31` and `routes/oauth.rs:33-35`. Pre-computed `DUMMY_HASH` via `LazyLock` ensures "user not found" takes the same wall-clock time as "wrong password". Applied independently across both web and OAuth login surfaces. Plus `crypto.rs:7-18` hashes both inputs with SHA-256 before constant-time XOR comparison -- neutralizes length-leak.
|
| 135 |
147 |
|
|
| 136 |
|
- |
5. **Performance -- Storage quota atomicity with idempotency checks (unexpectedly good):** `routes/storage/uploads.rs:206-219` checks idempotency before touching quotas, uses atomic `try_replace_storage` for file replacements. Sophisticated and rarely seen.
|
|
148 |
+ |
5. **Performance -- Discover page runs 5 facet queries in parallel via tokio::try_join! (unexpectedly good):** `routes/pages/public/discover.rs:342-361`. Type counts, tag counts, followed tags, AI tier counts, and price range counts all run concurrently. Textbook optimization that most production codebases miss. The codebase consistently avoids N+1 through batch loading across exports, items, and versions.
|
| 137 |
149 |
|
|
| 138 |
150 |
|
### Previous Surprises
|
| 139 |
151 |
|
|
|
152 |
+ |
**Run 24:** Claim token architecture, LATERAL join batch storage recalc, CSRF constant-time comparison, TOTP replay prevention via time step, storage quota atomicity + idempotency checks.
|
|
153 |
+ |
|
| 140 |
154 |
|
**Run 23:** Webhook signature gold standard, atomic storage quota enforcement, CSRF dual-layer extraction, archive decompression fallback (bad, fixed), advisory lock protocol.
|
| 141 |
155 |
|
|
| 142 |
156 |
|
**Run 22:** Webhook signature gold standard, cleanup scheduler durable queue, form error recovery, defense-in-depth 6 layers, export RAM buffering.
|
| 156 |
170 |
|
36 UUID newtypes via `define_pg_uuid_id!`, 25+ domain enums via `impl_str_enum!`, validated string types, Cents/PriceCents monetary newtypes with proptest coverage. All money math in integer cents (i32/i64), zero floating point in money paths. `SUM(BIGINT)::BIGINT` cast used consistently. License keys use CSPRNG (~55 bits entropy). FOR UPDATE row locking on license activation prevents TOCTOU.
|
| 157 |
171 |
|
|
| 158 |
172 |
|
### 3. Payment robustness
|
| 159 |
|
- |
Three-layer webhook idempotency. Bidirectional pending refund matching. Atomic promo code enforcement at DB level. `FOR UPDATE` row locking on tier deletion, license activation, pending refund claims. Self-purchase blocked across all paths. Cart PWYW minimum enforced. Tip amounts capped. Guest purchase claim tokens are cryptographically sound and idempotent.
|
|
173 |
+ |
Three-layer webhook idempotency. Bidirectional pending refund matching with FOR UPDATE SKIP LOCKED. Atomic promo code enforcement at DB level. `FOR UPDATE` row locking on tier deletion, license activation, pending refund claims. Self-purchase blocked across all paths. Cart PWYW minimum enforced. Tip amounts capped. Guest purchase claim tokens are cryptographically sound and idempotent.
|
| 160 |
174 |
|
|
| 161 |
175 |
|
### 4. Operational maturity
|
| 162 |
|
- |
All Run 22-23 fixes verified intact. Idempotency key scope widened (migration 106). Pending S3 deletions UNIQUE constraint (migration 107). Scheduler advisory lock pinned. Soft-delete purge handles version S3 keys. Confirm uploads wrapped in transactions. Discover page parallelized. Content exports stream one-by-one. N+1 queries consolidated.
|
|
176 |
+ |
All Run 23-24 fixes verified intact. Idempotency key scope widened (migration 106). Pending S3 deletions UNIQUE constraint (migration 107). Scheduler advisory lock pinned. Soft-delete purge handles version S3 keys. Confirm uploads wrapped in transactions. Discover page parallelized. Content exports stream one-by-one. N+1 queries consolidated.
|
| 163 |
177 |
|
|
| 164 |
178 |
|
## Weaknesses
|
| 165 |
179 |
|
|
| 166 |
|
- |
### Active (Run 24)
|
|
180 |
+ |
### Active (Run 25)
|
| 167 |
181 |
|
|
| 168 |
|
- |
- **Guest checkout promo code validation gap** -- Missing `starts_at` check allows future-dated codes to be used early by guest buyers. Easy fix (2 lines).
|
| 169 |
|
- |
- **Version replace storage counter corruption** -- Decrement-then-increment is not atomic. If increment fails, old storage is permanently lost from counter. Should use `try_replace_storage` or re-increment on failure.
|
|
182 |
+ |
- **Cart preflight SQL column name bug** -- `db/cart.rs:106` references `t.user_id` which does not exist on the `transactions` table (column is `buyer_id`). Runtime SQL error on cart toggle preflight.
|
|
183 |
+ |
- **Project deletion orphans S3 objects** -- `routes/api/projects.rs:270-282` CASCADEs DB records but does not enqueue S3 files for deletion or decrement storage counters. Permanent S3 orphans.
|
|
184 |
+ |
- **Content export buffers up to 2GB in memory** -- `routes/api/exports/content.rs:120-127` writes entire ZIP to in-memory Vec. OOM risk under concurrent export requests.
|
|
185 |
+ |
- **Non-atomic item cover image update** -- `routes/storage/images.rs:369-371` does 3 separate UPDATEs for cover_image_url, cover_s3_key, and cover_file_size_bytes. Crash between any two leaves inconsistent state.
|
|
186 |
+ |
- **Project image non-atomic storage swap** -- `routes/storage/images.rs:173-185` decrements then increments in two queries. Should use `try_replace_storage`.
|
|
187 |
+ |
- **Project image missing S3 deletion enqueue** -- `routes/storage/images.rs:173-179` does not enqueue old project image to `pending_s3_deletions`. Item images do clean up.
|
| 170 |
188 |
|
|
| 171 |
|
- |
### Resolved (Run 23)
|
|
189 |
+ |
### Resolved (Run 24)
|
| 172 |
190 |
|
|
| 173 |
|
- |
- ~~Version S3 key leak~~ -- Fixed. Old keys now enqueued via `pending_s3_deletions`.
|
| 174 |
|
- |
- ~~Archive decompression fallback~~ -- Fixed. 10x conservative multiplier on decompression error.
|
| 175 |
|
- |
- ~~Export memory buffering~~ -- Fixed. Files written to ZIP one-by-one.
|
| 176 |
|
- |
- ~~N+1 query patterns~~ -- Fixed. Cart and download access control consolidated.
|
|
191 |
+ |
- ~~Guest checkout promo code validation gap~~ -- Fixed. Added `starts_at` check.
|
|
192 |
+ |
- ~~Version replace storage counter corruption~~ -- Fixed. Uses `try_replace_storage`.
|
| 177 |
193 |
|
|
| 178 |
194 |
|
## Bug Reports by Axis
|
| 179 |
195 |
|
|
| 180 |
196 |
|
### Payments
|
| 181 |
|
- |
0 CRITICAL, 1 SERIOUS, 0 MINOR, 2 NOTE
|
|
197 |
+ |
0 CRITICAL, 1 SERIOUS, 0 MINOR, 4 NOTE
|
| 182 |
198 |
|
|
| 183 |
199 |
|
| # | Sev | Location | Description |
|
| 184 |
200 |
|
|---|-----|----------|-------------|
|
| 185 |
|
- |
| P1 | **SERIOUS** | `routes/api/guest_checkout.rs:91-140` | Missing `starts_at` promo code validation. All 3 authenticated paths (cart.rs:150, item.rs:127, subscriptions.rs:192) check it. |
|
| 186 |
|
- |
| P2 | NOTE | `db/license_keys.rs:158-233` | Activation limit enforced with FOR UPDATE + active count + idempotent upsert. Excellent. |
|
| 187 |
|
- |
| P3 | NOTE | `payments/webhooks.rs:48-61` | Webhook deduplication correctly prevents double-credit. Solid. |
|
|
201 |
+ |
| P1 | **SERIOUS** | `db/cart.rs:106` | SQL references `t.user_id` instead of `t.buyer_id`. Column does not exist on `transactions` table. Runtime SQL error on cart toggle preflight check. |
|
|
202 |
+ |
| P2 | NOTE | `payments/checkout.rs:33` | `CartLineItem.amount_cents` is `i64` while `CheckoutParams.amount_cents` is `Cents`. Inconsistency, not exploitable. |
|
|
203 |
+ |
| P3 | NOTE | `formatting.rs:11` | `format_price` uses `cents as f64 / 100.0`. Safe for typical amounts but code smell in monetary code. |
|
|
204 |
+ |
| P4 | NOTE | `db/transactions.rs:462` | `CreateProjectTransactionParams.amount_cents` is `i32` while item transactions use `Cents` (i64). |
|
|
205 |
+ |
| P5 | NOTE | `pricing.rs:134` | `FixedPricing::validate_amount` has no upper cap (PWYW caps at $10K). Low impact -- Stripe session amount is server-side. |
|
| 188 |
206 |
|
|
| 189 |
207 |
|
### Storage
|
| 190 |
|
- |
0 CRITICAL, 1 SERIOUS, 1 MINOR, 1 NOTE
|
|
208 |
+ |
0 CRITICAL, 1 SERIOUS, 3 MINOR, 2 NOTE
|
| 191 |
209 |
|
|
| 192 |
210 |
|
| # | Sev | Location | Description |
|
| 193 |
211 |
|
|---|-----|----------|-------------|
|
| 194 |
|
- |
| S1 | **SERIOUS** | `routes/storage/versions.rs:175-184` | Storage counter corruption: decrement at 175, try_increment at 182. If increment fails, decrement is not rolled back. |
|
| 195 |
|
- |
| S2 | MINOR | `db/pending_uploads.rs:17` | ON CONFLICT DO UPDATE refreshes created_at, letting repeated presign calls prevent garbage collection. |
|
| 196 |
|
- |
| S3 | NOTE | `routes/storage/uploads.rs:262` | Storage decrement rollback error swallowed with `.ok()`. Acceptable given background reconciliation. |
|
|
212 |
+ |
| S1 | **SERIOUS** | `routes/api/projects.rs:270-282` | Project deletion CASCADEs DB records but orphans all S3 objects (audio, cover images, version downloads, video). No storage decrement. |
|
|
213 |
+ |
| S2 | MINOR | `routes/storage/images.rs:369-371` | Item cover update: 3 separate UPDATEs for url, s3_key, file_size. Crash-unsafe. |
|
|
214 |
+ |
| S3 | MINOR | `routes/storage/images.rs:173-185` | Project image replace: non-atomic decrement/increment. Should use `try_replace_storage`. |
|
|
215 |
+ |
| S4 | MINOR | `routes/storage/images.rs:173-179` | Project image replace: old S3 object not enqueued to `pending_s3_deletions`. |
|
|
216 |
+ |
| S5 | NOTE | `routes/api/content_insertions.rs:301-309` | Delete-before-decrement order. If decrement fails, storage counter overstated until weekly recalc. |
|
|
217 |
+ |
| S6 | NOTE | `routes/storage/uploads.rs:250-253` | Dynamic SQL column names via `format!()` from internal enum. Not injection risk, but bypasses compile-time checking. |
|
| 197 |
218 |
|
|
| 198 |
219 |
|
### UX Wiring
|
| 199 |
|
- |
0 SERIOUS, 1 MINOR, 3 NOTE
|
|
220 |
+ |
0 CRITICAL, 0 SERIOUS, 2 MINOR, 3 NOTE
|
| 200 |
221 |
|
|
| 201 |
222 |
|
| # | Sev | Location | Description |
|
| 202 |
223 |
|
|---|-----|----------|-------------|
|
| 203 |
|
- |
| U1 | MINOR | `routes/pages/dashboard/forms.rs` | Blog editor doesn't preserve user input on slug validation error. |
|
| 204 |
|
- |
| U2 | NOTE | `routes/pages/public/discover.rs:85` | Page clamped to `.max(1)` but no upper bound. DB returns empty; no impact. |
|
| 205 |
|
- |
| U3 | NOTE | `templates/wizards/wizard_join.html` | Join wizard CSRF exemption justified (pre-auth) and documented. |
|
| 206 |
|
- |
| U4 | NOTE | `templates/dashboards/dashboard-blog-editor.html:42` | hx-vals with UUID template variable. Safe (UUIDs contain no special chars). |
|
|
224 |
+ |
| U1 | MINOR | `templates/pages/purchase.html:142` | Cart add-to-cart JS `fetch()` does not check response status before redirecting to `/cart`. 4xx/5xx silently redirect. |
|
|
225 |
+ |
| U2 | MINOR | `formatting.rs:8-9` | `format_price(-500)` produces `$-5.00` vs `format_revenue(-500)` = `-$5.00`. Inconsistent negative formatting. |
|
|
226 |
+ |
| U3 | NOTE | `routes/pages/public/discover.rs:85` | No upper bound on page param. `.max(1)` clamp only. DB returns empty; no impact. |
|
|
227 |
+ |
| U4 | NOTE | `routes/pages/public/join_wizard.rs:88-95` | Minimal email validation (checks `@` + `.`). Acceptable -- verification email is the real gate. |
|
|
228 |
+ |
| U5 | NOTE | `routes/pages/public/content/item.rs:340` | `cdn_base` variable scope spans entire monolithic function. Refactoring hazard, not a bug. |
|
| 207 |
229 |
|
|
| 208 |
230 |
|
### Security
|
| 209 |
|
- |
0 CRITICAL, 0 SERIOUS, 0 MINOR, 3 NOTE
|
|
231 |
+ |
0 CRITICAL, 0 SERIOUS, 0 MINOR, 7 NOTE
|
| 210 |
232 |
|
|
| 211 |
233 |
|
| # | Sev | Location | Description |
|
| 212 |
234 |
|
|---|-----|----------|-------------|
|
| 213 |
|
- |
| X1 | NOTE | `auth.rs:302-309` | verify_password is not independently constant-time on parse failure, but all callers use DUMMY_HASH. No exploitable timing channel. |
|
| 214 |
|
- |
| X2 | NOTE | `db/totp.rs:126-146` | Backup code DB lookup not constant-time. Standard practice; not exploitable over network. |
|
| 215 |
|
- |
| X3 | NOTE | `synckit_auth.rs:54-68` | JWT iat claim not validated. Standard; exp is validated. No impact. |
|
|
235 |
+ |
| X1 | NOTE | `auth.rs:206` | `MaybeUser` skips session revocation check (documented, intentional). OAuth compensates with manual validation. |
|
|
236 |
+ |
| X2 | NOTE | `routes/api/users/profile.rs:140-146` | Breached password change is advisory-only. Intentional UX decision. |
|
|
237 |
+ |
| X3 | NOTE | `db/totp.rs:22` | TOTP secret stored as plaintext in DB. Best practice would encrypt at rest. |
|
|
238 |
+ |
| X4 | NOTE | `synckit_auth.rs:57-58` | JWT `iat` claim not validated. Standard practice; `exp` is validated. |
|
|
239 |
+ |
| X5 | NOTE | `scanning/archive.rs:137-157` | Nested archives counted but not recursively scanned. YARA + ClamAV still scan outer bytes. |
|
|
240 |
+ |
| X6 | NOTE | `scanning/structural.rs:37-38` | `posix_spawn` and `dlopen` in suspicious symbols list may false-positive on legitimate macOS apps. |
|
|
241 |
+ |
| X7 | NOTE | `main.rs:116-121` | Session cookie not prefixed with `__Host-`. Low risk -- no user-content subdomains. |
|
| 216 |
242 |
|
|
| 217 |
243 |
|
### Performance
|
| 218 |
|
- |
0 CRITICAL, 0 SERIOUS, 2 MINOR, 1 NOTE
|
|
244 |
+ |
0 CRITICAL, 1 SERIOUS, 3 MINOR, 4 NOTE
|
| 219 |
245 |
|
|
| 220 |
246 |
|
| # | Sev | Location | Description |
|
| 221 |
247 |
|
|---|-----|----------|-------------|
|
| 222 |
|
- |
| F1 | MINOR | `metrics.rs:232` | Idempotency middleware: `.to_vec()` creates second allocation from Bytes. Minor efficiency loss. |
|
| 223 |
|
- |
| F2 | MINOR | `metrics.rs:228` | Response body >1MB returns Body::empty(). Only affects chunked responses without Content-Length (rare). |
|
| 224 |
|
- |
| F3 | NOTE | `routes/storage/uploads.rs:250-253` | Dynamic SQL column names via format!(). Hardcoded enum match arms -- not injection risk, but bypasses compile-time checking. |
|
|
248 |
+ |
| F1 | **SERIOUS** | `routes/api/exports/content.rs:120-127` | Content export accumulates up to 2GB in an in-memory `Vec<u8>` ZIP buffer. OOM risk under concurrent exports. |
|
|
249 |
+ |
| F2 | MINOR | `routes/api/exports/mod.rs:268-270` | N+1 query: collection items loaded per-collection in a loop (up to 50 queries). |
|
|
250 |
+ |
| F3 | MINOR | `build_runner.rs:396-408` | Build artifact read entirely into memory via `tokio::fs::read` before S3 upload. Mitigated by single-build advisory lock. |
|
|
251 |
+ |
| F4 | MINOR | `db/mod.rs:93` | `check_sandbox_cap` uses blocking `pg_advisory_lock` instead of `pg_try_advisory_lock`. Latency spike under burst. |
|
|
252 |
+ |
| F5 | NOTE | `scanning/mod.rs:68` | Scan pipeline takes `&[u8]` requiring full file in memory. Semaphore limits to 4 x 100MB = 400MB worst case. |
|
|
253 |
+ |
| F6 | NOTE | `lib.rs:238` | CSP header reads `S3_ENDPOINT` from `env::var` every request. Should use `state.config`. |
|
|
254 |
+ |
| F7 | NOTE | `routes/synckit/subscribe.rs:41-58` | SseConnectionGuard Drop has TOCTOU race on DashMap counter. Worst case: stale entry (harmless). |
|
|
255 |
+ |
| F8 | NOTE | `payments/checkout.rs:33` | `CartLineItem.amount_cents` is raw `i64` instead of `Cents` type. |
|
| 225 |
256 |
|
|
| 226 |
257 |
|
## Cross-Cutting Concerns
|
| 227 |
258 |
|
|
| 228 |
|
- |
### Promo code validation inconsistency (Payments + UX)
|
| 229 |
|
- |
The `starts_at` check is present in 4 checkout paths but missing from guest checkout. Pattern gap -- guest checkout was likely added later without copying all validations. A shared `validate_promo_code()` helper would prevent future divergence.
|
|
259 |
+ |
### Project deletion missing S3 cleanup (Storage + Payments)
|
|
260 |
+ |
Project deletion CASCADEs transactions, items, and versions in the DB but never touches S3 or storage counters. The `pending_s3_deletions` durable queue pattern exists and is used everywhere else (item deletion, version replacement, soft-delete purge). Its absence here is inconsistent and the most impactful finding of this audit.
|
| 230 |
261 |
|
|
| 231 |
|
- |
### Storage counter atomicity (Storage + Performance)
|
| 232 |
|
- |
The decrement-then-increment pattern in `versions.rs` is inconsistent with the atomic `try_replace_storage` used in `uploads.rs`. The versions path should use the same atomic pattern.
|
|
262 |
+ |
### Content export memory pressure (Storage + Performance)
|
|
263 |
+ |
The content export buffers an entire ZIP in memory (up to 2GB). This crosses both the storage axis (resource management) and performance axis (OOM risk). The route is rate-limited to 3 req/sec and requires authentication, limiting blast radius, but a single malicious creator could consume 2GB of server RAM.
|
|
264 |
+ |
|
|
265 |
+ |
### Image upload atomicity gap (Storage + UX)
|
|
266 |
+ |
Both project image and item cover image confirms have atomicity issues. The item path does 3 separate UPDATEs; the project path does non-atomic storage swap and skips S3 cleanup. The version upload path (`uploads.rs`) correctly uses `try_replace_storage` and `pending_s3_deletions` -- the image paths should follow the same pattern.
|
| 233 |
267 |
|
|
| 234 |
268 |
|
## Components Successfully Stress-Tested
|
| 235 |
269 |
|
|
| 245 |
279 |
|
### Security (17 vectors survived)
|
| 246 |
280 |
|
Virus scan bypass via ClamAV downtime (fail-closed), content-type spoofing, path traversal in archives, session fixation, timing-based user enumeration (dummy hash on all 3 login paths), brute force login (lockout), X-Forwarded-For spoofing (Cloudflare-aware), session reuse after password change, OAuth code replay, PKCE downgrade, token prediction (CSPRNG), CSRF on state-changing endpoints (constant-time), SSH command injection, passkey cloning (counter), TOTP replay (last_used_step), IDOR on passkeys/sessions, archive bombs (byte counting + 10x multiplier).
|
| 247 |
281 |
|
|
| 248 |
|
- |
### Performance (10 vectors survived)
|
| 249 |
|
- |
Connection pool exhaustion (bounded at 25), file scanning memory (semaphore of 4), SSE connection accumulation (bounded), scheduler job accumulation (advisory lock), background task leaks (monitored), ZIP bombs (byte counting), bulk operations (batch queries, 100-item cap), rate limiter bypass (Cloudflare-aware IP), concurrent scan memory pressure, large file handling (streamed).
|
|
282 |
+ |
### Performance (12 vectors survived)
|
|
283 |
+ |
Connection pool exhaustion (bounded at 25), file scanning memory (semaphore of 4), SSE connection accumulation (bounded at 10/user), scheduler job accumulation (advisory lock), background task leaks (monitored), ZIP bombs (byte counting), bulk operations (batch queries, 100-item cap), rate limiter bypass (Cloudflare-aware IP), concurrent scan memory pressure, large file handling (streamed uploads via presigned URLs), global lock contention (DashMap, no Mutex/RwLock), graceful shutdown (10s drain).
|
| 250 |
284 |
|
|
| 251 |
285 |
|
## Confidence Assessment
|
| 252 |
286 |
|
|
| 253 |
287 |
|
| Axis | Confidence | Notes |
|
| 254 |
288 |
|
|------|-----------|-------|
|
| 255 |
|
- |
| Payments | HIGH | One validation gap in guest checkout. Core payment logic excellent. |
|
| 256 |
|
- |
| Storage | HIGH | One error-path storage corruption. Normal path is atomic and correct. |
|
| 257 |
|
- |
| UX Wiring | HIGH | CSRF excellent. Templates safe. Minor UX polish only. |
|
| 258 |
|
- |
| Security | HIGH | No auth bypasses. DUMMY_HASH on all login paths. Fail-closed scanning. |
|
| 259 |
|
- |
| Performance | HIGH (current scale) | Pool size reasonable. Rate limiters well-tuned. Scheduler advisory-locked. |
|
|
289 |
+ |
| Payments | HIGH | One SQL column name bug in preflight. Core payment logic (webhooks, checkout, refunds) excellent. |
|
|
290 |
+ |
| Storage | HIGH | Project deletion S3 orphan gap. All other paths use durable deletion queue correctly. |
|
|
291 |
+ |
| UX Wiring | HIGH | CSRF excellent. Templates safe. Minor JS and formatting polish only. |
|
|
292 |
+ |
| Security | HIGH | No auth bypasses. DUMMY_HASH on all login paths. Fail-closed scanning. Zero SERIOUS findings. |
|
|
293 |
+ |
| Performance | HIGH (current scale) | Export memory buffering is the main concern. Pool, rate limiting, and concurrency are solid. |
|
| 260 |
294 |
|
|
| 261 |
295 |
|
## Metrics
|
| 262 |
296 |
|
|
| 263 |
|
- |
- Modules audited: 55+
|
| 264 |
|
- |
- Total cold spots: 0 (all 2 resolved in remediation)
|
| 265 |
|
- |
- Bugs by severity: 0 critical, 2 serious (all fixed), 3 minor (all fixed, 1 false positive), 10 note
|
|
297 |
+ |
- Modules audited: 60+
|
|
298 |
+ |
- Total cold spots: 5
|
|
299 |
+ |
- Bugs by severity: 0 critical, 3 serious, 8 minor, 20 note
|
| 266 |
300 |
|
- Axes at A or above: 5/5
|
| 267 |
301 |
|
|
| 268 |
302 |
|
## Axis Summary Grades
|
| 269 |
303 |
|
|
| 270 |
304 |
|
| Axis | Overall | Cold Spots | Mandatory Surprise |
|
| 271 |
305 |
|
|------|---------|------------|-------------------|
|
| 272 |
|
- |
| Payments | A | guest_checkout.rs (B) | Claim token architecture (good) |
|
| 273 |
|
- |
| Storage | A | versions.rs (B-) | LATERAL join batch storage recalc (good) |
|
| 274 |
|
- |
| UX Wiring | A | None | CSRF constant-time comparison (good) |
|
| 275 |
|
- |
| Security | A | None | TOTP replay prevention via time step (good) |
|
| 276 |
|
- |
| Performance | A | None | Storage quota atomicity + idempotency (good) |
|
|
306 |
+ |
| Payments | A | db/cart.rs (B+) | Pending refund queue with FOR UPDATE SKIP LOCKED (good) |
|
|
307 |
+ |
| Storage | A | projects.rs delete (B-), images.rs (B+) | Durable S3 deletion queue as transactional outbox (good) |
|
|
308 |
+ |
| UX Wiring | A | purchase.html (B+) | json_escape with HTML entity protection (good) |
|
|
309 |
+ |
| Security | A | None | Anti-timing dummy hash on ALL login paths (good) |
|
|
310 |
+ |
| Performance | A | exports/content.rs (B+) | Discover page 5-way parallel facet queries (good) |
|
| 277 |
311 |
|
|
| 278 |
312 |
|
## Recommended Priority Order
|
| 279 |
313 |
|
|
| 280 |
|
- |
1. **[SERIOUS]** Add `starts_at` validation to guest checkout -- 2 lines, copy from `cart.rs:150`
|
| 281 |
|
- |
2. **[SERIOUS]** Fix version replace storage rollback -- re-increment old storage on `try_increment` failure, or use `try_replace_storage`
|
| 282 |
|
- |
3. **[MINOR]** Change pending_uploads to `ON CONFLICT DO NOTHING` -- prevents staleness attack
|
| 283 |
|
- |
4. **[MINOR]** Blog editor: preserve input on validation error
|
| 284 |
|
- |
5. **[MINOR]** Idempotency middleware: use `std::str::from_utf8` instead of `String::from_utf8(to_vec())`
|
| 285 |
|
- |
6. **[DEFERRED]** Extract shared `validate_promo_code()` helper
|
|
314 |
+ |
1. **[SERIOUS]** Fix `db/cart.rs:106` -- change `t.user_id` to `t.buyer_id` (1 line)
|
|
315 |
+ |
2. **[SERIOUS]** Add S3 cleanup to project deletion -- collect S3 keys, enqueue to `pending_s3_deletions`, decrement storage (moderate effort)
|
|
316 |
+ |
3. **[SERIOUS]** Stream content export ZIP to S3 via multipart upload instead of in-memory buffer (medium effort)
|
|
317 |
+ |
4. **[MINOR]** Consolidate item cover image update into single UPDATE (trivial)
|
|
318 |
+ |
5. **[MINOR]** Use `try_replace_storage` for project image replace (low effort)
|
|
319 |
+ |
6. **[MINOR]** Enqueue old project image to `pending_s3_deletions` (trivial)
|
|
320 |
+ |
7. **[MINOR]** Add error status check to purchase.html cart JS (trivial)
|
|
321 |
+ |
8. **[MINOR]** Fix `format_price` negative formatting to match `format_revenue` (trivial)
|
|
322 |
+ |
9. **[MINOR]** Batch-load collection items in export to eliminate N+1 (low effort)
|
|
323 |
+ |
10. **[MINOR]** Switch `check_sandbox_cap` to `pg_try_advisory_lock` (trivial)
|
|
324 |
+ |
11. **[DEFERRED]** Stream build artifacts to S3 via multipart upload
|
|
325 |
+ |
12. **[DEFERRED]** Extract shared `validate_promo_code()` helper to prevent checkout path divergence
|
| 286 |
326 |
|
|
| 287 |
327 |
|
## Action Items
|
| 288 |
328 |
|
|
| 289 |
|
- |
### Run 24 (2026-05-09)
|
|
329 |
+ |
### Run 25 (2026-05-11)
|
| 290 |
330 |
|
|
| 291 |
|
- |
97. ~~**[SERIOUS]** Add `starts_at` validation to guest checkout~~ -- **Fixed.** Added `starts_at` check to `guest_checkout.rs:102-107`, matching all authenticated paths.
|
| 292 |
|
- |
98. ~~**[SERIOUS]** Fix version replace storage counter rollback~~ -- **Fixed.** `versions.rs` now uses atomic `try_replace_storage` for replacements, `try_increment_storage` for fresh uploads. No separate decrement/increment.
|
| 293 |
|
- |
99. ~~**[MINOR]** Change pending_uploads ON CONFLICT to DO NOTHING~~ -- **Fixed.** `pending_uploads.rs:17` now uses `DO NOTHING`, preventing staleness attack via repeated presign.
|
| 294 |
|
- |
100. ~~**[MINOR]** Blog editor: preserve form input on validation error~~ -- **False positive.** Blog editor uses JS `fetch()` API; form state is preserved on error.
|
| 295 |
|
- |
101. ~~**[MINOR]** Idempotency middleware: avoid double allocation~~ -- **Fixed.** `metrics.rs:232` now uses `std::str::from_utf8` with `to_owned()` only for the spawned task.
|
| 296 |
|
- |
102. **[DEFERRED]** Extract shared `validate_promo_code()` helper to prevent checkout path divergence.
|
|
331 |
+ |
103. **[SERIOUS]** Fix `db/cart.rs:106` -- change `t.user_id` to `t.buyer_id`
|
|
332 |
+ |
104. **[SERIOUS]** Add S3 cleanup + storage decrement to project deletion path
|
|
333 |
+ |
105. **[SERIOUS]** Stream content export ZIP to S3 instead of in-memory buffer
|
|
334 |
+ |
106. **[MINOR]** Consolidate item cover image into single UPDATE (`routes/storage/images.rs:369-371`)
|
|
335 |
+ |
107. **[MINOR]** Use `try_replace_storage` for project image replace (`routes/storage/images.rs:173-185`)
|
|
336 |
+ |
108. **[MINOR]** Enqueue old project image S3 key to `pending_s3_deletions`
|
|
337 |
+ |
109. **[MINOR]** Add HTTP error check to purchase.html cart add-to-cart JS
|
|
338 |
+ |
110. **[MINOR]** Fix `format_price` negative formatting to use `-$X.XX`
|
|
339 |
+ |
111. **[MINOR]** Batch-load collection items in export handler (`exports/mod.rs:268-270`)
|
|
340 |
+ |
112. **[MINOR]** Switch `check_sandbox_cap` to `pg_try_advisory_lock`
|
|
341 |
+ |
113. **[DEFERRED]** Stream build artifacts to S3 via multipart upload
|
|
342 |
+ |
114. **[DEFERRED]** Extract shared `validate_promo_code()` helper
|
|
343 |
+ |
|
|
344 |
+ |
### Run 24 (2026-05-09) -- All Fixed
|
|
345 |
+ |
|
|
346 |
+ |
97. ~~**[SERIOUS]** Add `starts_at` validation to guest checkout~~ -- **Fixed.**
|
|
347 |
+ |
98. ~~**[SERIOUS]** Fix version replace storage counter rollback~~ -- **Fixed.**
|
|
348 |
+ |
99. ~~**[MINOR]** Change pending_uploads ON CONFLICT to DO NOTHING~~ -- **Fixed.**
|
|
349 |
+ |
100. ~~**[MINOR]** Blog editor: preserve form input on validation error~~ -- **False positive.**
|
|
350 |
+ |
101. ~~**[MINOR]** Idempotency middleware: avoid double allocation~~ -- **Fixed.**
|
|
351 |
+ |
102. **[DEFERRED]** Extract shared `validate_promo_code()` helper -- carried to Run 25 item 114.
|
| 297 |
352 |
|
|
| 298 |
353 |
|
### Run 23 (2026-05-09) -- All Fixed
|
| 299 |
354 |
|
|
| 300 |
|
- |
88. ~~**[SERIOUS]** Enqueue old S3 key on version file replacement~~ -- **Fixed.**
|
| 301 |
|
- |
89. ~~**[SERIOUS]** Harden archive decompression fallback~~ -- **Fixed.**
|
| 302 |
|
- |
90. ~~**[SERIOUS]** Reserve promo code slot atomically at checkout creation~~ -- **Already implemented** (false positive).
|
| 303 |
|
- |
91. ~~**[MINOR]** Consolidate cart toggle into single JOIN query~~ -- **Fixed.**
|
| 304 |
|
- |
92. ~~**[MINOR]** Consolidate download access control into single query~~ -- **Fixed.**
|
| 305 |
|
- |
93. ~~**[MINOR]** Stream content exports~~ -- **Fixed.**
|
| 306 |
|
- |
94. ~~**[MINOR]** Tighten CSV email validation~~ -- **Fixed.**
|
| 307 |
|
- |
95. ~~**[DEFERRED]** Split oversized route files~~ -- **Fixed.**
|
| 308 |
|
- |
96. ~~**[DEFERRED]** Add README.md to server/~~ -- **Fixed.**
|
|
355 |
+ |
88-96. All 9 items verified fixed. See Run 24 verification table.
|
| 309 |
356 |
|
|
| 310 |
357 |
|
### Open (blocked on upstream)
|
| 311 |
358 |
|
|
| 314 |
361 |
|
25. Monitor aws-sdk-s3 for rustls-webpki 0.101.7 fix (RUSTSEC-2026-0049)
|
| 315 |
362 |
|
33. bincode unmaintained (RUSTSEC-2025-0141) -- upstream via syntect/yara-x, warning only
|
| 316 |
363 |
|
|
| 317 |
|
- |
## Previous Action Item Verification (Run 23)
|
|
364 |
+ |
## Previous Action Item Verification (Run 24)
|
| 318 |
365 |
|
|
| 319 |
366 |
|
| # | Item | Status |
|
| 320 |
367 |
|
|---|------|--------|
|
| 321 |
|
- |
| 88 | Enqueue old S3 key on version file replacement | **Fixed** (verified: versions.rs:203-204 enqueues old key) |
|
| 322 |
|
- |
| 89 | Harden archive decompression fallback | **Fixed** (verified: archive.rs:123 uses 10x multiplier) |
|
| 323 |
|
- |
| 90 | Reserve promo code slot atomically | **Already implemented** (false positive) |
|
| 324 |
|
- |
| 91 | Consolidate cart toggle | **Fixed** |
|
| 325 |
|
- |
| 92 | Consolidate download access control | **Fixed** |
|
| 326 |
|
- |
| 93 | Stream content exports | **Fixed** |
|
| 327 |
|
- |
| 94 | Tighten CSV email validation | **Fixed** |
|
| 328 |
|
- |
| 95 | Split oversized route files | **Fixed** |
|
| 329 |
|
- |
| 96 | Add README.md | **Fixed** |
|
|
368 |
+ |
| 97 | Add `starts_at` validation to guest checkout | **Fixed** (verified) |
|
|
369 |
+ |
| 98 | Fix version replace storage counter rollback | **Fixed** (verified: uses `try_replace_storage`) |
|
|
370 |
+ |
| 99 | Change pending_uploads ON CONFLICT to DO NOTHING | **Fixed** (verified) |
|
|
371 |
+ |
| 100 | Blog editor: preserve form input | **False positive** (JS fetch preserves state) |
|
|
372 |
+ |
| 101 | Idempotency middleware double allocation | **Fixed** (verified) |
|
|
373 |
+ |
| 102 | Extract shared validate_promo_code() | **Deferred** -- carried to Run 25 |
|
| 330 |
374 |
|
|
| 331 |
|
- |
9 of 9 Run 23 items verified fixed.
|
|
375 |
+ |
5 of 5 actionable Run 24 items verified fixed. 1 deferred item carried forward.
|
| 332 |
376 |
|
|
| 333 |
377 |
|
### Chronic Items (unfixed across 3+ consecutive runs)
|
| 334 |
378 |
|
|
| 335 |
|
- |
None. Both previous chronic items (README.md, oversized route files) resolved in Run 23.
|
|
379 |
+ |
None. The `validate_promo_code()` extraction has been deferred for 2 consecutive runs (24, 25) -- will become chronic if still open at Run 26.
|
| 336 |
380 |
|
|
| 337 |
|
- |
### False Positives Identified (Run 24)
|
|
381 |
+ |
### False Positives Identified (Run 25)
|
| 338 |
382 |
|
|
| 339 |
383 |
|
The following agent findings were investigated and determined not to be real bugs:
|
| 340 |
|
- |
- **verify_password timing attack** -- Mitigated by DUMMY_HASH in all callers (routes/auth.rs:111, routes/synckit/auth.rs:59+69, routes/oauth.rs:294). Hash always comes from DB or dummy.
|
| 341 |
|
- |
- **Backup code SQL WHERE timing** -- Standard database practice. ~1ms query time variance is indistinguishable from network jitter over TCP.
|
| 342 |
|
- |
- **SQL injection via format!** -- Column names are hardcoded string literals from exhaustive enum match. No user input.
|
| 343 |
|
- |
- **Multi-seller Stripe isolation** -- Requires API key compromise. Cart scoped to single seller at application level.
|
| 344 |
|
- |
- **JWT iat validation** -- Standard JWT behavior. exp is validated. No security impact.
|
| 345 |
|
- |
- **Password length not centralized** -- Present at all entry points (routes/auth.rs, routes/synckit/auth.rs:58, routes/oauth.rs). Argon2id has internal limits.
|
|
384 |
+ |
- **Tip checkout missing minimum charge** -- Route handler at `routes/stripe/checkout/tips.rs:54-57` validates minimum $1.00 (above Stripe's 50-cent minimum) before calling the payment provider.
|
|
385 |
+ |
- **FixedPricing no upper bound** -- Stripe session amount is set server-side. Client cannot override. The `validate_amount` function is for display/UX validation only.
|
|
386 |
+ |
- **MaybeUser session revocation bypass** -- Documented, intentional design. Used only for read-only/public pages. The one write endpoint (OAuth authorize) compensates with manual session validation.
|
|
387 |
+ |
- **Breached password advisory-only** -- Intentional UX decision. Server warns but doesn't block.
|
|
388 |
+ |
- **TOTP secret plaintext** -- Standard practice for TOTP. Encryption at rest would require key management infrastructure beyond current scale.
|
|
389 |
+ |
- **JWT iat not validated** -- Standard JWT behavior. exp is validated. No security impact.
|
|
390 |
+ |
- **Session cookie __Host- prefix** -- No user-content subdomains. SameSite=Lax + Secure + HttpOnly is sufficient.
|
|
391 |
+ |
- **Archive scanner no recursive scan** -- ClamAV and YARA scan the full outer bytes. Nested archive detection flags suspicious nesting (limit 3).
|
|
392 |
+ |
- **Structural analysis dlopen/posix_spawn** -- These are one signal among many; no false-positive reports in production.
|
| 346 |
393 |
|
|
| 347 |
|
- |
## Delta Since Run 23
|
|
394 |
+ |
## Delta Since Run 24
|
| 348 |
395 |
|
|
| 349 |
|
- |
### Fixed (from Run 23)
|
| 350 |
|
- |
All 9 Run 23 action items verified fixed. No regressions detected.
|
|
396 |
+ |
### Fixed (from Run 24)
|
|
397 |
+ |
5 of 5 actionable Run 24 items verified fixed. No regressions detected. 1 deferred item carried forward.
|
| 351 |
398 |
|
|
| 352 |
|
- |
### New Findings (not in Run 23)
|
| 353 |
|
- |
- Guest checkout missing `starts_at` promo validation (SERIOUS) -- new
|
| 354 |
|
- |
- Storage counter corruption on version replace failure (SERIOUS) -- new (distinct from Run 23's S3 key leak -- this is about counter rollback, not cleanup)
|
| 355 |
|
- |
- Pending uploads staleness attack via ON CONFLICT (MINOR) -- new
|
| 356 |
|
- |
- Blog editor input loss on error (MINOR) -- new
|
| 357 |
|
- |
- Idempotency middleware double allocation (MINOR) -- new
|
|
399 |
+ |
### New Findings (not in Run 24)
|
|
400 |
+ |
- Cart preflight SQL column name bug (SERIOUS) -- new, latent since cart was added
|
|
401 |
+ |
- Project deletion orphans S3 objects (SERIOUS) -- new finding (not previously audited at this depth)
|
|
402 |
+ |
- Content export 2GB memory buffering (SERIOUS) -- new finding (export was previously fixed for streaming writes, but ZIP output itself still buffered)
|
|
403 |
+ |
- Non-atomic item cover image 3-UPDATE (MINOR) -- new
|
|
404 |
+ |
- Non-atomic project image storage swap (MINOR) -- new (related to previously-noted fragile URL extraction)
|
|
405 |
+ |
- Project image missing S3 deletion enqueue (MINOR) -- new
|
|
406 |
+ |
- Purchase.html JS error handling gap (MINOR) -- new
|
|
407 |
+ |
- format_price negative formatting (MINOR) -- new
|
|
408 |
+ |
- Collection export N+1 query (MINOR) -- new
|
|
409 |
+ |
- check_sandbox_cap blocking advisory lock (MINOR) -- new
|
| 358 |
410 |
|
|
| 359 |
|
- |
### Grade Changes (after remediation)
|
| 360 |
|
- |
- guest_checkout.rs: (new) B -> A (starts_at added)
|
| 361 |
|
- |
- versions.rs: A -> B- -> A (atomic try_replace_storage)
|
| 362 |
|
- |
- pending_uploads.rs: B+ -> A (ON CONFLICT DO NOTHING)
|
| 363 |
|
- |
- metrics.rs: A- -> A (double allocation removed)
|
| 364 |
|
- |
- Overall: A (held, strengthened)
|
| 365 |
|
- |
- Cold spots: 0 -> 2 -> 0 (all resolved)
|
| 366 |
|
- |
- SERIOUS findings: 0 -> 2 -> 0 (all fixed)
|
|
411 |
+ |
### Grade Changes
|
|
412 |
+ |
- db/cart.rs: A -> B+ (SQL column bug found)
|
|
413 |
+ |
- routes/api/projects.rs (delete): (new) B- (S3 orphan gap)
|
|
414 |
+ |
- routes/api/exports/content.rs: A- -> B+ (memory buffering)
|
|
415 |
+ |
- routes/storage/images.rs: A -> B+ (atomicity + cleanup gaps)
|
|
416 |
+ |
- templates/purchase.html: (new) B+ (JS error handling)
|
|
417 |
+ |
- Overall: A (held)
|
|
418 |
+ |
- Cold spots: 0 -> 5
|
|
419 |
+ |
- SERIOUS findings: 0 -> 3
|
| 367 |
420 |
|
|
| 368 |
421 |
|
## Metrics Over Time
|
| 369 |
422 |
|
|