GioJSdocs
On this page

[security.csrf]

Cross-site request protection: unsafe requests from other sites are refused in Rust before the body is read or any app code runs.

gio.toml
[security.csrf]
trusted_origins = ["https://admin.example.com"]
exempt = ["/api/webhooks/*rest", "/auth/callback/apple"]

On by default with no configuration. It covers every method except GET, HEAD, OPTIONS and TRACE - page actions, route handlers, form posts - and its two lists also apply to the WebSocket origin check.

Reference

KeyDefaultDescription
enabledbooleantrueCheck unsafe requests. false lets any website send form posts and other state-changing requests with your visitors' cookies; only turn it off when every form carries its own CSRF token. The WebSocket origin check stays on.0 / false / empty: No check on unsafe methods. Warns.
trusted_originsstring[][]Other origins allowed to send unsafe requests and open WebSockets, as scheme://host[:port] (https://admin.example.com). The scheme is http or https; a path, query or user info is a startup error. A missing port is the scheme's default.
exemptstring[][]Paths that skip the check entirely, for endpoints other sites call on purpose: webhooks that send an Origin, OAuth/OIDC response_mode=form_post callbacks (Sign in with Apple, Microsoft Entra ID), SAML assertion consumer services, 3-D Secure returns. Patterns use the rule syntax (/api/webhooks/*rest, /hooks/:id) and match the canonical path.

Behavior

Each unsafe request is judged on headers browsers attach, in this order:

RequestResult
Path listed in exemptNot checked
Origin listed in trusted_originsAllowed
Sec-Fetch-Site: same-origin or none (typed URL, bookmark)Allowed
Sec-Fetch-Site: cross-site or same-site403
No Sec-Fetch-Site, Origin names the host the client addressedAllowed
No Sec-Fetch-Site, any other Origin (null included)403
Neither header (curl, server-to-server webhooks)Allowed: not sent by a browser page, so not forgeable by one
  • The host the client addressed is the Host header, or a trusted proxy's X-Forwarded-Host / Forwarded: host= (see [server] trusted_proxies).
  • A refused request gets 403 with Cache-Control: no-store and a plain-text body naming the keys to change:
text
403 Forbidden: cross-site POST blocked by CSRF protection - the browser sent it from https://evil.example (cross-site).
If that origin is yours, add it to [security.csrf] trusted_origins in gio.toml; to accept cross-site requests on a path (webhooks, OAuth/OIDC form_post or SAML callbacks, 3-D Secure payment returns), add the path to [security.csrf] exempt.
  • Each refused origin is logged once at warn level (up to 64 distinct ones), then at debug, so a page looping forged requests cannot flood the log.
  • Rust's own /_gio endpoints are not covered: they have their own checks (the revalidation endpoint uses a bearer token).

Startup warnings

WhenStartup warning
enabled = false[security.csrf] enabled = false: any website can send form posts and other unsafe requests to this server with your visitors' cookies - prefer listing origins in [security.csrf] trusted_origins, or public endpoints in [security.csrf] exempt

Errors

  • [security.csrf] trusted_origins entry "admin.example.com" is not an origin such as "https://admin.example.com" (scheme http or https, no path)
  • [security.csrf] exempt entry "api/webhooks": pattern must start with '/': api/webhooks

Examples

An admin app on another origin

gio.toml
[security.csrf]
trusted_origins = ["https://admin.example.com", "http://localhost:5173"]

Sign in with Apple form_post callback

Apple posts the user's browser back to your callback from its own site, so the browser labels the request cross-site. Exempt the path; the handler verifies the state and the token itself:

gio.toml
[security.csrf]
exempt = ["/auth/callback/apple"]

Forms with their own tokens

gio.toml
[security.csrf]
enabled = false          # every form posts a token the app verifies (logs a warning)

Good to know

  • The check runs before the body is read, before rules, routing and Node - a forged request never costs a render. It runs inside rate limiting, so forged requests still use up budget.
  • Behind a proxy that rewrites Host to an internal name, every same-origin browser request looks cross-origin. Pass the host through (nginx proxy_set_header Host $host), or list the proxy in trusted_proxies so its X-Forwarded-Host counts.
  • Keep GET handlers free of side effects and keep session cookies SameSite=Lax: the check covers unsafe methods only.

Not configurable

  • Sec-Fetch-Site: same-site is refused like cross-site: a sibling subdomain can be controlled by someone else. List such origins in trusted_origins.
  • Requests with neither Sec-Fetch-Site nor Origin always pass.

Version history

VersionChanges
v0.1.0-beta.8Introduced, on by default. enabled = false logs a startup warning.