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:
mix
2026-10-16 23:16:27 +03:00
co-authored by Claude Opus 5.5
parent af778445d4
commit 503939c6cf
9 changed files with 58 additions and 28 deletions
+8 -8
View File
@@ -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 == "" {