origin-strict: a change with neither Sec-Fetch-Site nor Origin is refused
Agreed with the owner on 2026-10-13 (in place of csrf-tokens), taken up on
2026-10-16 ("Да"). originAllowed (internal/web/security.go) returns false
in the "neither header" branch: such a POST is answered 403 and logged
like a cross-origin one. It was let through until now as an accepted
risk, which left a browser that sends neither open to a request forged by
any site. A browser that follows Fetch § 3.2 never sends a POST without
Origin; one too old to (Firefox before 70) can no longer save anything.
What posts to the panel without a browser: the e2e suite's client, which
now sends Origin on every form (panelClient.postForm); cmd/panel's tests
already did. Nothing else posts to it (the CLI runs in the container).
Tests: security_test's case of no headers is refused; e2e posts with
neither header and gets 403 (testFormPostWithTheBrowsersOrigin). go vet +
go test (26 packages) and make e2e on the test server for this tree.
Docs: security.md - the accepted risk struck, the ADR's decision and
trade-off say refused, the origin-check bullet; architecture § Web,
guide § the proxy; CHANGELOG [Unreleased] § Security; roadmap row done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -146,14 +146,14 @@ func originAllowed(r *http.Request) bool {
|
||||
// https.
|
||||
origin := r.Header.Get("Origin")
|
||||
if origin == "" {
|
||||
// Neither header. A client this old cannot be checked at all; it is
|
||||
// let through as the risk consciously accepted (single-admin panel,
|
||||
// the administrator picks the browser). Turning this return into
|
||||
// false is the whole of the stricter policy. By Fetch § 3.2 a browser
|
||||
// that follows it never sends a POST without Origin; Firefox did for a
|
||||
// same-origin form before 70 (MDN browser-compat-data 6.0.0,
|
||||
// http.headers.Origin).
|
||||
return true
|
||||
// Neither header: nothing says where the request came from, so it is
|
||||
// refused (origin-strict, owner 2026-10-13; until 2.10.4 it was let
|
||||
// through as an accepted risk). By Fetch § 3.2 a browser that follows
|
||||
// it never sends a POST without Origin; Firefox did for a same-origin
|
||||
// form before 70 (MDN browser-compat-data 6.0.0, http.headers.Origin),
|
||||
// and such a browser can no longer save anything here. A script that
|
||||
// posts to the panel sends Origin itself (test/e2e, panelClient).
|
||||
return false
|
||||
}
|
||||
u, err := url.Parse(origin)
|
||||
if err != nil || u.Host == "" {
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestOriginCheck(t *testing.T) {
|
||||
req *http.Request
|
||||
wantRejected bool
|
||||
}{
|
||||
{"no headers at all is let through (accepted risk)", post("", ""), false},
|
||||
{"no headers at all is refused (origin-strict)", post("", ""), true},
|
||||
{"same-origin", post("same-origin", "https://panel.example.com"), false},
|
||||
{"same-site, i.e. a neighbouring subdomain", post("same-site", ""), true},
|
||||
{"cross-site", post("cross-site", "https://evil.example.net"), true},
|
||||
|
||||
Reference in New Issue
Block a user