Refuse OPTIONS * like any other API request (closes #4)
check / check (push) Successful in 1m16s
check / check (push) Successful in 1m16s
net/http answered "OPTIONS *" itself, with 200, before the router, so it skipped the credential check and the security headers. The server now passes it to the router, which refuses it with 401 like any other request without the credential. A test sends it to a running server, since the handler alone never sees it. Model: opus-5-5
This commit is contained in:
@@ -4,7 +4,9 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -175,6 +177,49 @@ func TestNoPathIsExempt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestOptionsAsterisk: "OPTIONS *" is refused like any other request.
|
||||
// net/http would answer it before the handler, so this request goes to
|
||||
// a running server rather than to its handler.
|
||||
func TestOptionsAsterisk(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv := newAPI("", &fakeClient{})
|
||||
|
||||
listener, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
go func() { _ = srv.Serve(listener) }()
|
||||
|
||||
t.Cleanup(func() { _ = srv.Close() })
|
||||
|
||||
req, err := http.NewRequestWithContext(t.Context(), http.MethodOptions,
|
||||
"http://"+listener.Addr().String(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// The request line becomes "OPTIONS * HTTP/1.1".
|
||||
req.URL.Opaque = "*"
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusUnauthorized || string(body) != unauthorized {
|
||||
t.Errorf("OPTIONS *: %d %q, want 401 %q", resp.StatusCode, body, unauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHeaders: every response, whatever its status, carries the
|
||||
// security headers, and none lets another origin in.
|
||||
func TestHeaders(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user