Cello v1.2.2 — Security & Bug Fixes¶
Release Date: June 14, 2026 License: MIT Python: 3.12+
Overview¶
Cello v1.2.2 is a patch release that fixes one critical security bug, one security vulnerability, and two correctness bugs discovered during a thorough code review of v1.2.1. All changes are fully backwards-compatible.
Upgrade immediately if you use CSRF protection or authentication middleware skip-paths.
Security Fixes¶
CSRF Double-Submit Cookie Was HttpOnly (Critical)¶
Symptom: AJAX applications and SPAs using CsrfMiddleware received 403 on every POST/PUT/DELETE request, even when the CSRF token was correctly set.
Root cause: The CSRF double-submit cookie was set with HttpOnly, which prevents JavaScript from reading cookie values. The double-submit pattern requires JavaScript to read the cookie and copy its value into the X-CSRF-Token request header. With HttpOnly set, this was impossible — every state-changing request failed CSRF validation.
Fix: Removed HttpOnly from the CSRF token cookie. The CSRF cookie is intentionally readable by JavaScript (that is the design). Your session cookie, which holds sensitive data, remains HttpOnly.
// Now works — JavaScript can read the CSRF cookie
const csrf = document.cookie.match(/_csrf=([^;]+)/)?.[1];
fetch("/api/submit", {
method: "POST",
headers: { "X-CSRF-Token": csrf, "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice" }),
});
Auth Middleware skip_path Allowed Prefix Bypass (Security)¶
Symptom: Calling jwt.skip_path("/health") to exclude the health-check endpoint from authentication also excluded /healthz, /healthy, /health-admin, and any other path whose string representation started with /health.
Root cause: All middleware used request.path.starts_with(skip_path) with no boundary check. An attacker aware of a skipped path could craft a URL that bypasses authentication.
Affected middleware: JwtAuth, BasicAuth, ApiKeyAuth, RateLimitMiddleware, SessionMiddleware, CsrfMiddleware, GuardsMiddleware.
Fix: Paths are now only skipped when:
- The path exactly equals the pattern, or
- The path starts with
{pattern}/(sub-path match).
# Before: /healthz was also skipped — security bypass
jwt_auth.skip_path("/health")
# After: only /health and /health/* are skipped
jwt_auth.skip_path("/health")
Bug Fixes¶
Rate Limiter Fixed-Window Counter Broke After First Window¶
Symptom: After the first time-window expired, the FixedWindowStore rate limiter stopped enforcing limits entirely — every request was allowed regardless of how many had been made.
Root cause: When the window rolled over, the per-client entry's count was reset to 0 but window_start was never updated to the new window ID. Every subsequent request also saw window_start != current_window and reset the counter to 0 again, making the limiter permanently ineffective after the first window.
Fix: window_start is now updated to current_window alongside the count reset.
Unused mut Warnings in Template Engine Tests¶
Five test functions in minijinja_engine.rs declared let mut env = Environment::new() where mut was not needed (the variable was never mutated after construction). These were cleaned up to eliminate Clippy warnings.
Files Changed¶
| File | Change |
|---|---|
src/middleware/csrf.rs | Remove HttpOnly from CSRF cookie |
src/middleware/mod.rs | Add path_matches_skip() shared helper |
src/middleware/auth.rs | Use path_matches_skip() in JWT/Basic/ApiKey |
src/middleware/session.rs | Use path_matches_skip() |
src/middleware/rate_limit.rs | Use path_matches_skip(); fix window_start reset |
src/middleware/guards.rs | Use path_matches_skip() |
src/minijinja_engine.rs | Remove unused mut from test bindings |
Upgrade¶
Drop-in replacement for v1.2.1. No API changes.