test: security regression — IDOR payment-check scoping, CORS preflight 403, rate limiter pruning, services error leakage

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-08-22 00:34:51 +01:00
co-authored by Sisyphus
parent a358c8c0ea
commit 4d179385e8
4 changed files with 302 additions and 2 deletions
+65 -2
View File
@@ -177,8 +177,8 @@ func TestCORSPreflight_RejectsUnlistedOrigin(t *testing.T) {
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusNoContent {
t.Errorf("expected preflight 204, got %d", rr.Code)
if rr.Code != http.StatusForbidden {
t.Errorf("expected preflight 403 for unlisted origin, got %d", rr.Code)
}
if got := rr.Header().Get("Access-Control-Allow-Origin"); got != "" {
t.Errorf("expected no Access-Control-Allow-Origin on preflight for unlisted origin, got %q", got)
@@ -290,3 +290,66 @@ func TestIsWeakJWTSecret_EntropyGate(t *testing.T) {
t.Errorf("expected high-entropy secret %q to be accepted", weak[len(weak)-1])
}
}
// ============================================================
// CORS fix: OPTIONS from non-allowed origins returns 403
// ============================================================
// TestCORSPreflight_NonAllowedOrigin_Returns403 verifies the CORS fix: an
// OPTIONS preflight from a non-allowed origin must return 403 Forbidden
// (not 204 No Content) and must NOT include the Access-Control-Allow-Origin
// header, so a browser cannot be tricked into believing CORS is granted.
func TestCORSPreflight_NonAllowedOrigin_Returns403(t *testing.T) {
t.Setenv("FRONTEND_ORIGIN", "https://app.example.com")
handler := corsMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodOptions, "/api/bookings", nil)
req.Header.Set("Origin", "https://evil.com")
req.Header.Set("Access-Control-Request-Method", "POST")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusForbidden {
t.Errorf("expected 403 Forbidden for non-allowed origin OPTIONS, got %d", rr.Code)
}
if got := rr.Header().Get("Access-Control-Allow-Origin"); got != "" {
t.Errorf("expected no Access-Control-Allow-Origin header for non-allowed origin, got %q", got)
}
// Also verify no CORS headers are leaked.
if got := rr.Header().Get("Access-Control-Allow-Methods"); got != "" {
t.Errorf("expected no Access-Control-Allow-Methods header for non-allowed origin, got %q", got)
}
}
// TestCORSPreflight_AllowedOrigin_Returns204 verifies that an OPTIONS preflight
// from a configured origin returns 204 No Content with proper CORS headers.
func TestCORSPreflight_AllowedOrigin_Returns204(t *testing.T) {
t.Setenv("FRONTEND_ORIGIN", "https://app.example.com")
handler := corsMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodOptions, "/api/bookings", nil)
req.Header.Set("Origin", "https://app.example.com")
req.Header.Set("Access-Control-Request-Method", "POST")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusNoContent {
t.Errorf("expected 204 No Content for allowed origin OPTIONS, got %d", rr.Code)
}
if got := rr.Header().Get("Access-Control-Allow-Origin"); got != "https://app.example.com" {
t.Errorf("expected Access-Control-Allow-Origin %q, got %q", "https://app.example.com", got)
}
if got := rr.Header().Get("Vary"); got != "Origin" {
t.Errorf("expected Vary: Origin header, got %q", got)
}
if got := rr.Header().Get("Access-Control-Allow-Methods"); got == "" {
t.Errorf("expected Access-Control-Allow-Methods header to be set")
}
if got := rr.Header().Get("Access-Control-Allow-Headers"); got == "" {
t.Errorf("expected Access-Control-Allow-Headers header to be set")
}
}