Skip to content

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

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

pip install --upgrade cello-framework==1.2.2

Drop-in replacement for v1.2.1. No API changes.