mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 12:42:49 +00:00
2ce3605454
Two routes registered without a mux method pattern changed state on any
method. The demo-mode read-only guard and the CSRF check both admit GET and
HEAD as reads, and the SameSite=Lax session cookie rides cross-site
top-level GET navigations, so each was writable by a safe method:
- /api/discovery/settings had no method check, so a GET or HEAD carrying a
JSON body changed the discovery staleness threshold, including on a
DEMO_MODE instance.
- SP-initiated SAML logout at /api/saml/{id}/logout cleared the session and
redirected to the IdP on GET. Nothing in the UI calls it, the /api/logout
fallback it delegates to is already POST-only, and the IdP's
LogoutResponse returns to /slo, not here.
Add requireRequestMethod (405 with Allow) as the shared handler-level
guard. Discovery settings keeps PUT, its documented method, and POST, which
it accepted before; SAML logout takes POST only. An audit of every route in
internal/api found no other caller-requested write reachable by GET or HEAD
beyond the Assistant session routes guarded in #2670. GET flows the
protocol requires (OIDC login and callback, SAML login initiation and SLO,
magic-link and cloud handoff, checkout bridges) and reads that run lazy
initialization or idempotent reconciliation are left as they are. One
known exception is deferred: single-resource report generation still runs
on GET and can call the configured AI narrator, which records provider
usage in the cost ledger; moving it behind POST changes the report API the
UI downloads from, so it is tracked separately.
24 lines
788 B
Go
24 lines
788 B
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// requireRequestMethod admits only the listed methods to a handler that
|
|
// changes state, answering anything else with 405 and an Allow header. Bare
|
|
// mux patterns and path-dispatched routes do not filter methods, and GET/HEAD
|
|
// pass both the demo-mode read-only guard and the CSRF check while
|
|
// SameSite=Lax session cookies ride cross-site top-level GET navigations, so a
|
|
// mutating handler must refuse safe methods itself.
|
|
func requireRequestMethod(w http.ResponseWriter, r *http.Request, allowed ...string) bool {
|
|
for _, method := range allowed {
|
|
if r.Method == method {
|
|
return true
|
|
}
|
|
}
|
|
w.Header().Set("Allow", strings.Join(allowed, ", "))
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return false
|
|
}
|