Skip to content

CSRF functionality for templates#1087

Draft
pydanny wants to merge 4 commits intomainfrom
csrf-add
Draft

CSRF functionality for templates#1087
pydanny wants to merge 4 commits intomainfrom
csrf-add

Conversation

@pydanny
Copy link
Member

@pydanny pydanny commented Mar 1, 2026

What

  • Implements CSRF for Jinja templates
  • Cookies set and controlled in CSRFMiddleware

Fixes issue #110

Note: This is a bit complex, will compare with @Isaac-Flath's https://github.com/kentro-tech/aircsrf/blob/main/src/air_csrf/middleware.py and consider a rewrite.

AI Provenance (omit for human‑authored PRs)

Used AI to help write tests and address some typing issues

  • Tool: Codex
  • Model: GPT-5.3-Coxes
  • Thread / prompt: Implement tests and clean out typing issues

Pattern

This adds a csrf.py utility module and otherwise touches middleware.py and templating.py.

Reviewer Focus

  • The tests may not be a true reflection of real world behaviors
  • Is not yet a default behavior, so currently breaks jinja templates if CSRFMiddleware is not configured
  • Doesn't yet have documentation
  • Only covers Jinja projects

Checklist

  • Diff contains only changes for this task — no unrelated refactoring or cleanup
  • Addresses exactly one issue or feature
  • New or changed behavior has test coverage
  • This is the simplest viable approach
  • AI provenance section removed or accurate

@pydanny pydanny added python Pull requests that update python code feature: core labels Mar 1, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements CSRF support for Jinja template workflows in Air by introducing a shared CSRF utility module, adding a new CSRFMiddleware for double-submit cookie validation, and exposing template helpers to render/read CSRF tokens in Jinja templates.

Changes:

  • Added CSRFMiddleware to set/validate CSRF cookies and verify submitted tokens (form field or X-CSRF-Token header).
  • Added csrf.py utilities and wired Jinja globals (csrf_token(), csrf_input()) into the template environment.
  • Added Jinja + middleware tests and a test template to validate end-to-end behavior.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
uv.lock Bumps ty dev dependency version.
src/air/csrf.py New shared CSRF constants + request/scope helpers.
src/air/middleware.py Adds CSRFMiddleware implementing double-submit cookie CSRF validation.
src/air/templating.py Registers Jinja globals for CSRF token + hidden input rendering.
src/air/init.py Re-exports CSRFMiddleware from the package root.
tests/test_templating.py Adds test asserting CSRF helpers render expected token/hidden input.
tests/test_middleware.py Adds integration-style tests for valid/missing/invalid/header CSRF submissions.
tests/templates/csrf_form.html New template exercising csrf_input() and csrf_token() globals.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +169 to +172
if submitted_token is None:
buffered_body = await _read_body(receive)
submitted_token = await self._extract_form_token(scope, headers, buffered_body)
downstream_receive = _receive_from_body(buffered_body)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSRFMiddleware buffers the entire request body into memory when the CSRF header is missing (_read_body + _extract_form_token). For large multipart/form-data submissions (e.g., file uploads), this can cause unbounded memory usage and makes a straightforward DoS vector. Consider enforcing a maximum buffered size (via Content-Length + incremental limit), or requiring the header token for multipart requests and skipping body buffering in that case, returning 413/403 when the body is too large to safely buffer.

Copilot uses AI. Check for mistakes.
cookie_samesite: Literal["lax", "strict", "none"] = "lax",
cookie_max_age: int | None = None,
error_message: str = "CSRF verification failed.",
) -> None:
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SameSite=None cookies are rejected by modern browsers unless they also have the Secure attribute. Since this middleware exposes cookie_samesite and cookie_secure independently, setting cookie_samesite="none" with cookie_secure=False will silently break CSRF (cookie never stored/sent). Consider validating these options in __init__ and raising a ValueError (or auto-enabling cookie_secure) when cookie_samesite == "none" and cookie_secure is false.

Suggested change
) -> None:
) -> None:
if cookie_samesite == "none" and not cookie_secure:
raise ValueError(
'CSRFMiddleware: cookie_samesite="none" requires cookie_secure=True '
"for modern browsers to accept the CSRF cookie."
)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: core python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants