Context: Astro returns a 403 with this exact message when its CSRF middleware rejects a form POST. Most of the time the submission is legitimate and only looks cross-site because the app runs behind a reverse proxy: the fix is to declare your production domain in `security.allowedDomains` in `astro.config` so Astro trusts the proxy's forwarded headers.

```
Cross-site POST form submissions are forbidden
``

1. Confirm the pattern first: the 403 hits form POSTs in production (behind Cloudflare, Cloud Run, Caddy, or any TLS-terminating proxy) while the same forms work in local dev. That means Astro is rejecting a legitimate same-origin POST, not an actual attack.

2. In `astro.config`, add your real production hostname so Astro trusts the proxy's forwarded headers (requires Astro 5.14.2 or later):

```
security: {
  allowedDomains: [{ hostname: 'YOUR_SITE_HOST', protocol: 'https' }]
}
```

Match the hostname and protocol the browser actually uses.

3. Rebuild and redeploy, then resubmit the form. Expected: the POST returns 200 or 302 instead of 403.

4. If you serve several subdomains, list each one explicitly, or use a wildcard pattern like `**.YOUR_SITE_HOST` to cover them all.

Variant phrasings: 403 Cross-site POST form submissions are forbidden; every form POST fails in production with a CSRF 403; sign-out or login form blocked behind a proxy.

Root cause: Astro's `checkOrigin` middleware (on by default since Astro 4.9) compares the browser's Origin header against the URL Astro reconstructs for the request. Behind a proxy, Astro ignores `X-Forwarded-Host` and `X-Forwarded-Proto` unless the domain is listed in `security.allowedDomains`, so it rebuilds the URL with the internal scheme and host. The Origin header no longer matches, and every form-encoded POST, PATCH, PUT, or DELETE is rejected as cross-site. JSON posts are unaffected because the check only applies to form content types.

Edge cases: `checkOrigin` only runs for on-demand rendered (SSR) pages, so static output never triggers this error. The allowlist entry `[{}]` trusts any forwarded host, which reopens host-header injection, so reserve it for trusted dev setups. On Astro below 5.14.2, `allowedDomains` does not exist, so upgrade instead of looking for another config knob.

When not to use: do not set `checkOrigin` to false globally to clear the error. That removes CSRF protection from every form in the app and only hides the misconfiguration.