Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ var defaultIgnoredHeaders = []string{
"if-match", "if-none-match", "if-modified-since",
"last-modified", "transfer-encoding", "vary", "x-forwarded-for",
"x-forwarded-proto", "x-real-ip", "x-request-id",
"request-start-time", // Added by some API clients for timing
}

// GetEffectiveStrictIgnoredHeaders returns the list of headers to ignore
Expand Down
4 changes: 4 additions & 0 deletions errors/strict_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ func UndeclaredPropertyError(
direction string,
requestPath string,
requestMethod string,
specLine int,
specCol int,
) *ValidationError {
dirStr := direction
if dirStr == "" {
Expand All @@ -47,6 +49,8 @@ func UndeclaredPropertyError(
RequestMethod: requestMethod,
ParameterName: name,
Context: truncateForContext(value),
SpecLine: specLine,
SpecCol: specCol,
}
}

Expand Down
12 changes: 12 additions & 0 deletions errors/strict_errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ func TestUndeclaredPropertyError(t *testing.T) {
"request",
"/users",
"POST",
42,
10,
)

assert.NotNil(t, err)
Expand All @@ -30,6 +32,8 @@ func TestUndeclaredPropertyError(t *testing.T) {
assert.Equal(t, "/users", err.RequestPath)
assert.Equal(t, "POST", err.RequestMethod)
assert.Equal(t, "extra", err.ParameterName)
assert.Equal(t, 42, err.SpecLine)
assert.Equal(t, 10, err.SpecCol)
}

func TestUndeclaredPropertyError_Response(t *testing.T) {
Expand All @@ -41,12 +45,16 @@ func TestUndeclaredPropertyError_Response(t *testing.T) {
"response",
"/items/123",
"GET",
100,
5,
)

assert.NotNil(t, err)
assert.Contains(t, err.Message, "response property 'undeclared'")
assert.Contains(t, err.Reason, "id, name")
assert.Equal(t, "{...}", err.Context) // Map truncated
assert.Equal(t, 100, err.SpecLine)
assert.Equal(t, 5, err.SpecCol)
}

func TestUndeclaredPropertyError_EmptyDirection(t *testing.T) {
Expand All @@ -58,9 +66,13 @@ func TestUndeclaredPropertyError_EmptyDirection(t *testing.T) {
"", // Empty direction defaults to "request"
"/test",
"POST",
0, // Zero values for unknown location
0,
)

assert.Contains(t, err.Message, "request property")
assert.Equal(t, 0, err.SpecLine)
assert.Equal(t, 0, err.SpecCol)
}

func TestUndeclaredHeaderError(t *testing.T) {
Expand Down
53 changes: 53 additions & 0 deletions helpers/parameter_utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,59 @@ func ExtractSecurityForOperation(request *http.Request, item *v3.PathItem) []*ba
return schemes
}

// ExtractSecurityHeaderNames extracts header names from applicable security schemes.
// Returns header names from apiKey schemes with in:"header", plus "Authorization"
// for http/oauth2/openIdConnect schemes.
//
// This function is used by strict mode validation to recognize security headers
// as "declared" headers that should not trigger undeclared header errors.
func ExtractSecurityHeaderNames(
security []*base.SecurityRequirement,
securitySchemes map[string]*v3.SecurityScheme,
) []string {
if security == nil || securitySchemes == nil {
return nil
}

seen := make(map[string]bool)
var headers []string

for _, sec := range security {
if sec == nil || sec.ContainsEmptyRequirement {
continue // No security required for this option
}

if sec.Requirements == nil {
continue
}

for pair := sec.Requirements.First(); pair != nil; pair = pair.Next() {
schemeName := pair.Key()
scheme, ok := securitySchemes[schemeName]
if !ok || scheme == nil {
continue
}

var headerName string
switch strings.ToLower(scheme.Type) {
case "apikey":
if strings.ToLower(scheme.In) == Header {
headerName = scheme.Name
}
case "http", "oauth2", "openidconnect":
headerName = "Authorization"
}

if headerName != "" && !seen[strings.ToLower(headerName)] {
seen[strings.ToLower(headerName)] = true
headers = append(headers, headerName)
}
}
}

return headers
}

func cast(v string) any {
if v == "true" || v == "false" {
b, _ := strconv.ParseBool(v)
Expand Down
Loading