web: refuse writes that come from somebody else's page

The session cookie is scoped to the parent domain and is SameSite=Lax, and
every *.parodia.dev host is the same site as Pete as far as Lax is concerned.
So a form on any sibling subdomain could post a bet, a mischief order or an
equip change and the browser would attach whoever's signed-in cookie to it.
Nothing on the server looked at where a request came from.

The whole handler now sits behind http.NewCrossOriginProtection. It reads
Sec-Fetch-Site (falling back to Origin against Host) and refuses a
state-changing request from any other origin with a 403. GETs are untouched,
and so is anything that carries neither header, which is exactly what
gogobee's bearer-token calls from Go look like.
This commit is contained in:
prosolis
2026-09-22 22:54:38 -07:00
parent 21ed051bc2
commit 4eaf8ae366
2 changed files with 59 additions and 1 deletions
+8 -1
View File
@@ -353,9 +353,16 @@ func New(cfg config.WebConfig, sources []config.SourceConfig, postingEnabled boo
}
}
// Every state-changing request from a browser must come from this site's
// own origin. The session cookie is scoped to the parent domain and is
// SameSite=Lax, and every *.parodia.dev host counts as the same site, so
// without this a form on any sibling subdomain could bet, order or equip as
// whoever is signed in. It keys off Sec-Fetch-Site (falling back to Origin
// vs Host) and passes requests carrying neither, so gogobee's bearer-token
// calls from Go are untouched. GET/HEAD/OPTIONS are never blocked.
s.srv = &http.Server{
Addr: cfg.ListenAddr,
Handler: s.hostRouter(mux),
Handler: http.NewCrossOriginProtection().Handler(s.hostRouter(mux)),
ReadHeaderTimeout: 5 * time.Second,
}
return s, nil