Files
T
cgalo5758 0b28a9dc29 Remediate security audit findings
- Replace gorilla/csrf with net/http CrossOriginProtection
- Require valkey-password and add TLS options for session store
- End session at /logout and revoke refresh tokens
- Re-derive identity and roles from provider every five minutes
- Process each Stripe webhook event in its own Temporal workflow
- Give each outbox entry its own workflow with Temporal retries
- Guard against stale Stripe events with provider timestamps
- Derive transport security from base-url scheme
2026-09-09 13:25:43 -05:00

70 lines
2.7 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
package middleware
import (
"fmt"
"net/http"
)
// CSRFConfig configures cross-origin request protection.
//
// This wraps net/http's CrossOriginProtection, which rejects non-safe
// cross-origin browser requests by reading Sec-Fetch-Site (sent by every
// browser since 2023) and falling back to comparing the Origin header's
// hostname with Host. There is no token: nothing is issued, stored in a
// cookie, embedded in a form, or echoed in a header.
//
// It replaced gorilla/csrf in 2026-09 because that library carries
// GO-2025-3884 (CVE-2025-47909) with no fixed release: its trusted-origin
// comparison ignored the scheme, so a policy written for https was satisfied
// by plain http. AddTrustedOrigin here requires a full origin, scheme
// included, which is the defect's direct answer.
//
// Two consequences worth knowing. GET, HEAD and OPTIONS are always allowed, so
// no handler may change state on those methods. And a request carrying neither
// Sec-Fetch-Site nor Origin is treated as same-origin or non-browser and
// allowed, which is what lets server-to-server callers through.
type CSRFConfig struct {
// TrustedOrigins are additional origins allowed to make non-safe
// requests, each a full origin such as "https://console.example.coop".
// An entry without a scheme is rejected at construction.
TrustedOrigins []string
// BypassPatterns are net/http.ServeMux patterns exempted from the check
// entirely, for endpoints that authenticate their caller some other way
// (a provider webhook verifying its own signature). Each one is a
// deliberate hole; see the server's exempt-path list for the contract.
BypassPatterns []string
// DenyHandler serves a rejected request. When nil the standard library's
// default 403 is used.
DenyHandler http.Handler
}
// CSRF returns middleware enforcing cross-origin protection.
//
// An invalid trusted origin is a configuration error the caller must handle;
// it is never silently dropped, because a trusted origin that fails to
// register would leave a legitimate deployment rejecting its own form posts.
func CSRF(config CSRFConfig) (Middleware, error) {
protection := http.NewCrossOriginProtection()
for _, origin := range config.TrustedOrigins {
if err := protection.AddTrustedOrigin(origin); err != nil {
return nil, fmt.Errorf("trusted origin %q: %w", origin, err)
}
}
for _, pattern := range config.BypassPatterns {
protection.AddInsecureBypassPattern(pattern)
}
if config.DenyHandler != nil {
protection.SetDenyHandler(config.DenyHandler)
}
return func(next http.Handler) http.Handler {
return protection.Handler(next)
}, nil
}