|
2 | 2 |
|
3 | 3 | All notable changes to RustChan will be documented in this file. |
4 | 4 |
|
5 | | -## [1.0.10] — 2026-03-06 |
| 5 | +## [1.0.11] — 2026-03-06 |
| 6 | + |
| 7 | +### Security — Critical |
| 8 | + |
| 9 | +- **CRIT-1: Security headers** — added `Content-Security-Policy`, `Strict-Transport-Security`, |
| 10 | + and `Permissions-Policy` response headers to `build_router()`. CSP restricts scripts, styles, |
| 11 | + and media to `'self'`, preventing external payload loading or data exfiltration via XSS. |
| 12 | + HSTS enforces HTTPS for one year including subdomains. Permissions-Policy disables |
| 13 | + camera, microphone, and geolocation APIs. |
| 14 | + |
| 15 | +- **CRIT-2: Proxy-aware IP extraction in post handlers** — all post-creation handlers |
| 16 | + (`create_thread`, `post_reply`) now use the proxy-aware `extract_ip()` / `ClientIp` extractor |
| 17 | + instead of the raw socket address. Bans and rate limits are now effective when the server |
| 18 | + runs behind nginx or any other reverse proxy. |
| 19 | + |
| 20 | +- **CRIT-3: Rate limiting on GET endpoints** — the catalog, search, thread-update, and JSON |
| 21 | + API endpoints were previously completely unrate-limited, allowing trivial DoS via unbounded |
| 22 | + LIKE scans or 200-thread catalog loads. A separate GET rate limiter (60 req/min per IP) |
| 23 | + has been applied to all read-heavy routes. |
| 24 | + |
| 25 | +- **CRIT-4: Zip-bomb protection on restore handlers** — all four backup restore handlers |
| 26 | + previously used `std::io::copy` with no size or entry limits. Each entry is now capped at |
| 27 | + 1 GiB via `.take()` and extraction aborts if more than 50 000 entries are encountered, |
| 28 | + preventing a 1 KB zip from exhausting disk space. |
| 29 | + |
| 30 | +- **CRIT-5: IP address hashing** — raw IP addresses are no longer stored in the `ACTIVE_IPS` |
| 31 | + `DashMap` or printed to stdout/logs. All IP tracking now uses the same HMAC-keyed hash |
| 32 | + (`hash_ip`) used elsewhere, preventing IP exposure in coredumps or log aggregators. |
| 33 | + |
| 34 | +- **CRIT-6: Admin login brute-force lockout** — the admin login endpoint previously had no |
| 35 | + per-IP failure tracking beyond the global rate limit (~600 attempts/hour). Failed login |
| 36 | + attempts are now counted per IP and the account is locked for a progressive delay after |
| 37 | + 5 consecutive failures. |
| 38 | + |
| 39 | +- **CRIT-7: Constant-time CSRF token comparison** — CSRF token validation was using |
| 40 | + standard `==` string comparison, leaking prefix-matching information via timing side |
| 41 | + channel. Comparison now uses `subtle::ct_eq` for constant-time equality. |
| 42 | + |
| 43 | +- **CRIT-8: Poll input length and count caps** — poll questions and options had no |
| 44 | + server-side limits, allowing megabytes of text or thousands of options per submission. |
| 45 | + Poll options are now capped at 10, each option at 128 characters, and the question |
| 46 | + at 256 characters. |
| 47 | + |
| 48 | +### Security — High |
| 49 | + |
| 50 | +- **HIGH-1: Admin session cookie `Max-Age`** — the session cookie previously had no |
| 51 | + `Max-Age` or `Expires` attribute, causing browsers to persist it indefinitely after the |
| 52 | + tab was closed. The cookie now carries a `Max-Age` matching the server-side |
| 53 | + `session_duration` config value. |
| 54 | + |
| 55 | +- **HIGH-2: Database connection pool timeout** — the r2d2 connection pool had no |
| 56 | + acquisition timeout, allowing `spawn_blocking` threads to block forever under load and |
| 57 | + exhaust the Tokio thread pool. A 5-second `connection_timeout` has been added to the |
| 58 | + pool builder. |
| 59 | + |
| 60 | +- **HIGH-3: Per-route body limits on small-payload endpoints** — the global 50 MiB |
| 61 | + `DefaultBodyLimit` was applied to every route including login, vote, report, and appeal, |
| 62 | + causing the server to buffer 50 MiB before returning 400 on oversized requests. These |
| 63 | + four endpoints now carry an explicit 64 KiB per-route limit. |
| 64 | + |
| 65 | +- **HIGH-4: Open redirect hardening on `return_to`** — the logout `return_to` parameter |
| 66 | + check only blocked `//` and `..`, allowing backslash (`\`) and URL-encoded variants |
| 67 | + (`%5C`) to redirect to external hosts on some browsers. The filter now also rejects any |
| 68 | + value containing a literal backslash or its percent-encoded form. |
| 69 | + |
| 70 | +- **HIGH-5: Proxy-aware IP in `file_report` and `submit_appeal`** — these two handlers |
| 71 | + were using the raw socket IP for per-IP rate limiting, making the limit ineffective |
| 72 | + behind a reverse proxy. Both now use the `ClientIp` extractor, consistent with post |
| 73 | + handlers (same root cause as CRIT-2). |
| 74 | + |
| 75 | +- **HIGH-6: Exponential backoff with jitter in worker error recovery** — all four |
| 76 | + background workers recovered from DB errors with a flat 2-second sleep, meaning all |
| 77 | + workers could retry simultaneously and storm the database. Error recovery now uses |
| 78 | + exponential backoff (500 ms base, doubling per failure, capped at 60 s) with 0–500 ms |
| 79 | + random jitter to spread retries across workers. |
| 80 | + |
| 81 | +- **HIGH-7: TOCTOU race in file deduplication** — concurrent identical uploads could both |
| 82 | + pass the hash check before either had written to `file_hashes`, causing the second |
| 83 | + `record_file_hash` call to return a 500. The insert now uses `INSERT OR IGNORE` so the |
| 84 | + second concurrent insert is silently a no-op instead of an error. |
| 85 | + |
| 86 | + |
6 | 87 |
|
7 | 88 | ### Added |
8 | 89 | - **Per-post inline ban+delete** — the admin toolbar that appears on every post in |
|
0 commit comments