security: harden first preview boundaries
verify / verify (push) Successful in 3m1s

Sanitized snapshot of private source 13a965dd6ea705dd92499f7dbeaa00c25c15247d. Require same-origin evidence for unsafe methods, fail closed on invalid authentication middleware configuration, and bound untrusted request metadata.

AI-Assistance: OpenAI Codex assisted implementation, testing, and security review.
Signed-off-by: Cole Speelman <crspeelman@gmail.com>
This commit is contained in:
2026-08-16 19:20:14 -04:00
parent df946d888e
commit a18f1dd22a
13 changed files with 181 additions and 12 deletions
+11 -2
View File
@@ -40,7 +40,7 @@ func SetSession(response http.ResponseWriter, config CookieConfig, token string,
if err := config.Validate(); err != nil {
return err
}
if token == "" || len(token) > 128 {
if len(token) < 32 || len(token) > 128 {
return errors.New("authhttp: invalid session token")
}
sameSite := config.SameSite
@@ -64,16 +64,25 @@ func ClearSession(response http.ResponseWriter, config CookieConfig) error {
}
func SessionToken(request *http.Request, config CookieConfig) (string, bool) {
if config.Validate() != nil {
return "", false
}
cookie, err := request.Cookie(config.Name)
if err != nil || cookie.Value == "" || len(cookie.Value) > 128 {
if err != nil || len(cookie.Value) < 32 || len(cookie.Value) > 128 {
return "", false
}
return cookie.Value, true
}
func Optional(service *auth.Service, config CookieConfig) func(http.Handler) http.Handler {
configurationValid := service != nil && config.Validate() == nil
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
if !configurationValid {
response.Header().Set("Cache-Control", "no-store")
http.Error(response, "authentication unavailable", http.StatusServiceUnavailable)
return
}
if token, ok := SessionToken(request, config); ok {
if principal, err := service.Session(request.Context(), token); err == nil {
request = request.WithContext(auth.WithPrincipal(request.Context(), principal))
+38
View File
@@ -36,6 +36,13 @@ func TestCookieRequiresHostPrefix(t *testing.T) {
}
}
func TestSessionCookieRejectsShortToken(t *testing.T) {
config := CookieConfig{Name: "__Host-app_session", Lifetime: time.Hour}
if err := SetSession(httptest.NewRecorder(), config, "predictable", time.Unix(100, 0)); err == nil {
t.Fatal("short session token accepted")
}
}
func TestCSRFUsesSessionAndPurpose(t *testing.T) {
token := strings.Repeat("s", 43)
csrf, err := CSRFToken(token, "profile:update")
@@ -65,6 +72,37 @@ func TestOptionalFailsClosedWhenSessionStorageIsUnavailable(t *testing.T) {
}
}
func TestOptionalFailsClosedWhenConfigurationIsInvalid(t *testing.T) {
for _, test := range []struct {
name string
service *auth.Service
config CookieConfig
}{
{name: "nil service", config: CookieConfig{Name: "__Host-app_session", Lifetime: time.Hour}},
{name: "invalid cookie", service: mustAuthService(t), config: CookieConfig{Name: "session", Lifetime: time.Hour}},
} {
t.Run(test.name, func(t *testing.T) {
handler := Optional(test.service, test.config)(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
t.Fatal("handler ran with invalid authentication configuration")
}))
response := httptest.NewRecorder()
handler.ServeHTTP(response, httptest.NewRequest(http.MethodGet, "https://example.test/", nil))
if response.Code != http.StatusServiceUnavailable || response.Header().Get("Cache-Control") != "no-store" {
t.Fatalf("status=%d cache=%q", response.Code, response.Header().Get("Cache-Control"))
}
})
}
}
func mustAuthService(t *testing.T) *auth.Service {
t.Helper()
service, err := auth.New(authHTTPRepository{}, auth.Options{})
if err != nil {
t.Fatal(err)
}
return service
}
type authHTTPRepository struct{ err error }
func (authHTTPRepository) CreateUser(context.Context, auth.User, string) error { return nil }