REVIEW-001 (M0) perimeter findings: - H1: resolveForwarded walked X-Forwarded-For left→right and returned the leftmost untrusted entry. An append-style proxy (nginx's default) appends the peer it saw to the RIGHT of whatever the client sent, so the leftmost entry is attacker-controlled — a fresh rate-limit bucket per request (A3) and a forged client_ip in the audit trail (A11/D-031). Walk right→left and return the first untrusted entry, falling back to the peer. - L1s: the session cookie now carries the `__Host-` prefix when it is Secure (https), so it is pinned to the exact origin and cannot be shadowed from a sibling subdomain. Conditional on purpose: a browser rejects a `__Host-` cookie without Secure, which would break the http loopback bootstrap. - L4s: add Permissions-Policy, Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy. - Add httpx.IsLoopbackIP: a RESOLVED client IP test that fails closed on an empty/unparseable value (the setup-mode gate, H2), sharing a classifier with IsLoopbackHost whose "unknown" direction is the opposite, each fail-closed for its own decision. Tests updated: the unit test encoding the vulnerable leftmost expectation is rewritten, plus right→left/forged-entry/host-classification coverage.
39 lines
1.6 KiB
Go
39 lines
1.6 KiB
Go
package httpx
|
|
|
|
import "net/http"
|
|
|
|
// ContentSecurityPolicy is the strict default CSP (SPEC-003 §4). It allows only
|
|
// same-origin assets; the PWA manifest and service worker are same-origin too.
|
|
const ContentSecurityPolicy = "default-src 'self'; " +
|
|
"img-src 'self' data:; " +
|
|
"style-src 'self'; " +
|
|
"script-src 'self'; " +
|
|
"connect-src 'self'; " +
|
|
"object-src 'none'; " +
|
|
"base-uri 'none'; " +
|
|
"form-action 'self'; " +
|
|
"frame-ancestors 'none'"
|
|
|
|
// SetSecurityHeaders writes the baseline security headers on every response
|
|
// (SPEC-003 §4). HSTS is set only when the app is served over https (secure).
|
|
func SetSecurityHeaders(w http.ResponseWriter, secure bool) {
|
|
h := w.Header()
|
|
h.Set("X-Content-Type-Options", "nosniff")
|
|
h.Set("Referrer-Policy", "no-referrer")
|
|
h.Set("X-Frame-Options", "DENY")
|
|
h.Set("Content-Security-Policy", ContentSecurityPolicy)
|
|
// L4s — the isolation trio: deny the powerful features the app never uses,
|
|
// and keep the document and its subresources same-origin (so a compromised
|
|
// or mis-hosted sibling cannot open, embed, or read MyDay).
|
|
h.Set("Permissions-Policy", PermissionsPolicy)
|
|
h.Set("Cross-Origin-Opener-Policy", "same-origin")
|
|
h.Set("Cross-Origin-Resource-Policy", "same-origin")
|
|
if secure {
|
|
h.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
|
}
|
|
}
|
|
|
|
// PermissionsPolicy disables the browser features MyDay does not use (L4s).
|
|
// An empty allowlist means "no origin, including self, may use it".
|
|
const PermissionsPolicy = "accelerometer=(), camera=(), geolocation=(), " +
|
|
"gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
|