[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
| Key | Default | Description |
|---|---|---|
enabledboolean | true | Check 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. |
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:
| Request | Result |
|---|---|
Path listed in exempt | Not checked |
Origin listed in trusted_origins | Allowed |
Sec-Fetch-Site: same-origin or none (typed URL, bookmark) | Allowed |
Sec-Fetch-Site: cross-site or same-site | 403 |
No Sec-Fetch-Site, Origin names the host the client addressed | Allowed |
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
Hostheader, or a trusted proxy'sX-Forwarded-Host/Forwarded: host=(see[server] trusted_proxies). - A refused request gets
403withCache-Control: no-storeand 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
warnlevel (up to 64 distinct ones), then atdebug, so a page looping forged requests cannot flood the log. - Rust's own
/_gioendpoints are not covered: they have their own checks (the revalidation endpoint uses a bearer token).
Startup warnings
| When | Startup 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
Hostto an internal name, every same-origin browser request looks cross-origin. Pass the host through (nginxproxy_set_header Host $host), or list the proxy intrusted_proxiesso itsX-Forwarded-Hostcounts. - Keep
GEThandlers free of side effects and keep session cookiesSameSite=Lax: the check covers unsafe methods only.
Not configurable
Sec-Fetch-Site: same-siteis refused likecross-site: a sibling subdomain can be controlled by someone else. List such origins intrusted_origins.- Requests with neither
Sec-Fetch-SitenorOriginalways pass.
Related
- Security guide: CSRF protection
[security.websocket][security]- Forms & Mutations
- Turning Protections On and Off
Version history
| Version | Changes |
|---|---|
v0.1.0-beta.8 | Introduced, on by default. enabled = false logs a startup warning. |