test(security): prove cross-origin form POSTs are rejected #280

Öppen
supernaut vill sammanfoga 3 incheckningar från s[2]s in i main
Ägare

What

  • Test that cross-origin form POSTs are rejected (403) on every route that exports POST.
  • security.checkOrigin: true set explicitly in astro.config.mjs.
  • README no longer claims Accept-Language detection (Refs #177).
  • Version 1.15.2.

Why

Astro's origin check is the only CSRF protection on the state-changing routes, and nothing failed if it went away. The README claimed detection that does not exist.

How tested

src/test/csrf-origin.test.ts starts dist/server/entry.mjs on a free port. It finds routes by scanning src/pages for export const POST, so new routes are covered. Per route: cross-origin application/x-www-form-urlencoded, multipart/form-data and text/plain expect 403. Same-origin control expects not 403.

Negative control: with checkOrigin: false the test fails (30 failed, 11 passed). With true: 41 passed.

It runs in CI under the existing pnpm test step, after Build. The CI does not run Playwright, so e2e was not an option. Locally the file skips if dist/ is missing. Under CI it fails instead.

Findings

Astro only checks form content types (urlencoded, multipart, text/plain) and requests with no content type. A cross-origin request with another content type (for example application/json) passes the check.

  • /api/captcha/redeem reads JSON. It uses no session and no cookie, so CSRF does not apply.
  • Every other POST route calls request.formData(), which fails on a JSON body.

No route is exposed today. A future JSON route that uses the session cookie would be. Cross-origin JSON with a cookie needs a CORS preflight, which is not granted. The session cookie is SameSite=Lax, a second layer.

Open questions

  • #177: the issue also asks whether detection is wanted. Not decided here. The README now says it is not used. The public ADR claim is tracked separately.
## What - Test that cross-origin form POSTs are rejected (403) on every route that exports `POST`. - `security.checkOrigin: true` set explicitly in `astro.config.mjs`. - README no longer claims `Accept-Language` detection (Refs #177). - Version 1.15.2. ## Why Astro's origin check is the only CSRF protection on the state-changing routes, and nothing failed if it went away. The README claimed detection that does not exist. ## How tested `src/test/csrf-origin.test.ts` starts `dist/server/entry.mjs` on a free port. It finds routes by scanning `src/pages` for `export const POST`, so new routes are covered. Per route: cross-origin `application/x-www-form-urlencoded`, `multipart/form-data` and `text/plain` expect 403. Same-origin control expects not 403. Negative control: with `checkOrigin: false` the test fails (30 failed, 11 passed). With `true`: 41 passed. It runs in CI under the existing `pnpm test` step, after `Build`. The CI does not run Playwright, so e2e was not an option. Locally the file skips if `dist/` is missing. Under `CI` it fails instead. ## Findings Astro only checks form content types (urlencoded, multipart, text/plain) and requests with no content type. A cross-origin request with another content type (for example `application/json`) passes the check. - `/api/captcha/redeem` reads JSON. It uses no session and no cookie, so CSRF does not apply. - Every other POST route calls `request.formData()`, which fails on a JSON body. No route is exposed today. A future JSON route that uses the session cookie would be. Cross-origin JSON with a cookie needs a CORS preflight, which is not granted. The session cookie is SameSite=Lax, a second layer. ## Open questions - #177: the issue also asks whether detection is wanted. Not decided here. The README now says it is not used. The public ADR claim is tracked separately.
supernaut lade till 3 incheckningar 2026-10-03 00:11:24 +00:00
Astro's origin check is the only CSRF protection on the state-changing routes. Nothing failed if it was removed. The new test starts the built server and posts cross-origin form requests to every route that exports POST, expecting 403, with a same-origin control. checkOrigin is now set explicitly so a default change cannot drop it.
The README claimed English was auto-detected from Accept-Language. No such detection exists. Swedish is served at / and English under /en/, chosen with the switcher.

Refs #177
test(security): fail fast when the test server crashes
Alla kontroller lyckades
ci / ci (pull_request) Successful in 2m0s
f68055d957
The spawn ignored stdio and had no exit listener, so a crashing server cost the full poll loop and failed as 'server did not start' with no cause. Stderr is now buffered and an early exit rejects at once with its tail.
supernaut schemalade den här ändringsförfrågan för automatisk sammanfogning när alla kontroller lyckas 2026-10-03 00:11:54 +00:00
Alla kontroller lyckades
ci / ci (pull_request) Successful in 2m0s
Obligatorisk
Detaljer
den här ändringsförfrågan är blockerad eftersom den är föråldrad.
Den här grenen är föråldrad gentemot basgrenen
Du är inte behörig att sammanfoga den här ändringsförfrågan.
Visa kommandoradsinstruktioner

Checka ut

Checka ut en ny gren från din projektkatalog och testa ändringarna.
git fetch -u origin test/csrf-origin-check:test/csrf-origin-check
git switch test/csrf-origin-check
Logga in för att delta i denna konversation.
Inga granskare
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-web!280
Ingen beskrivning angiven.