Stop allowing credentialed cross-origin requests (closes #261)
check / check (push) Failing after 3s
check / check (push) Failing after 3s
CORS() allowed every origin with credentials, so a page on another origin of the same site could read what the signed-in user sees. It now uses the options of the CONVENTIONS.md template: every origin as *, no credentials. It applies only to GET /api/v1/server and GET /.well-known/healthcheck.json, the public read-only routes, since REPO_POLICIES.md forbids * on authenticated routes. Every other route sends no Access-Control-Allow-* header. Model: opus-5-5
This commit is contained in:
1 parent
72d8f2c12b
commit
9c8b70ed36
4 files changed
+101
-21
No files matched your search
@@ -30,7 +30,6 @@ func (srv *Server) SetupRoutes() {
|
||||
srv.router.Use(srv.mw.Metrics())
|
||||
}
|
||||
|
||||
srv.router.Use(srv.mw.CORS())
|
||||
srv.router.Use(srv.mw.CSP())
|
||||
srv.router.Use(middleware.Timeout(routeTimeout))
|
||||
|
||||
@@ -45,7 +44,7 @@ func (srv *Server) SetupRoutes() {
|
||||
}
|
||||
|
||||
// Health check.
|
||||
srv.router.Get(
|
||||
srv.router.With(srv.mw.CORS()).Get(
|
||||
"/.well-known/healthcheck.json",
|
||||
srv.handlers.HandleHealthCheck(),
|
||||
)
|
||||
@@ -69,7 +68,7 @@ func (srv *Server) SetupRoutes() {
|
||||
}
|
||||
|
||||
func (srv *Server) setupAPIv1(router chi.Router) {
|
||||
router.Get(
|
||||
router.With(srv.mw.CORS()).Get(
|
||||
"/server",
|
||||
srv.handlers.HandleServerInfo(),
|
||||
)
|
||||
|
||||
@@ -6,8 +6,10 @@ package server_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -188,6 +190,88 @@ func TestServeAfterStopDoesNotStopApp(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCORSHeaders checks the Access-Control-Allow-* headers on the
|
||||
// answers to requests from another origin. The public, read-only
|
||||
// routes answer with Access-Control-Allow-Origin: * alone. Routes
|
||||
// that need the cookie, and a preflight to one, answer with none.
|
||||
func TestCORSHeaders(t *testing.T) {
|
||||
var srv *server.Server
|
||||
|
||||
startApp(t, fx.Populate(&srv))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
path string
|
||||
requestMethod string
|
||||
wantHeaders map[string]string
|
||||
}{
|
||||
{
|
||||
name: "server information",
|
||||
method: http.MethodGet,
|
||||
path: "/api/v1/server",
|
||||
wantHeaders: map[string]string{"Access-Control-Allow-Origin": "*"},
|
||||
},
|
||||
{
|
||||
name: "health check",
|
||||
method: http.MethodGet,
|
||||
path: "/.well-known/healthcheck.json",
|
||||
wantHeaders: map[string]string{"Access-Control-Allow-Origin": "*"},
|
||||
},
|
||||
{
|
||||
name: "send",
|
||||
method: http.MethodPost,
|
||||
path: "/api/v1/messages",
|
||||
},
|
||||
{
|
||||
name: "state",
|
||||
method: http.MethodGet,
|
||||
path: "/api/v1/state",
|
||||
},
|
||||
{
|
||||
name: "preflight for send",
|
||||
method: http.MethodOptions,
|
||||
path: "/api/v1/messages",
|
||||
requestMethod: http.MethodPost,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
request := httptest.NewRequestWithContext(
|
||||
t.Context(), test.method,
|
||||
"http://chat.example.com"+test.path, nil,
|
||||
)
|
||||
request.Header.Set("Origin", "http://blog.example.com")
|
||||
|
||||
if test.requestMethod != "" {
|
||||
request.Header.Set(
|
||||
"Access-Control-Request-Method", test.requestMethod,
|
||||
)
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
srv.ServeHTTP(recorder, request)
|
||||
|
||||
gotHeaders := map[string]string{}
|
||||
|
||||
for name := range recorder.Header() {
|
||||
if strings.HasPrefix(name, "Access-Control-Allow-") {
|
||||
gotHeaders[name] = recorder.Header().Get(name)
|
||||
}
|
||||
}
|
||||
|
||||
if !maps.Equal(gotHeaders, test.wantHeaders) {
|
||||
t.Errorf(
|
||||
"Access-Control-Allow-* headers are %v, want %v",
|
||||
gotHeaders, test.wantHeaders,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// startApp starts the app newApp builds, with the HTTP port
|
||||
// set to 0, so the system gives the server a free port.
|
||||
func startApp(
|
||||
|
||||
Reference in new issue
Block a user