@@ -569,7 +569,7 @@ func (h *openAPIRouteHandler) validate(r *http.Request) []string {
569569 var bodyData any
570570 if jsonErr := json .Unmarshal (bodyBytes , & bodyData ); jsonErr != nil {
571571 errs = append (errs , fmt .Sprintf ("request body contains invalid JSON: %v" , jsonErr ))
572- } else if bodyErrs := validateJSONValue (bodyData , "body" , mediaType .Schema ); len (bodyErrs ) > 0 {
572+ } else if bodyErrs := validateJSONValue (bodyData , "request body" , mediaType .Schema ); len (bodyErrs ) > 0 {
573573 errs = append (errs , bodyErrs ... )
574574 }
575575 }
@@ -584,8 +584,11 @@ func (h *openAPIRouteHandler) validate(r *http.Request) []string {
584584
585585// responseCapturingWriter buffers the response body, status code, and headers
586586// so we can validate them against the OpenAPI spec before sending to the client.
587+ // It uses its own header map to prevent leaked headers reaching the client when
588+ // validation fails and a different (500) response needs to be sent.
587589type responseCapturingWriter struct {
588590 underlying http.ResponseWriter
591+ headers http.Header // own header map; copied to underlying only on flush
589592 body bytes.Buffer
590593 statusCode int
591594 headerSent bool
@@ -594,14 +597,15 @@ type responseCapturingWriter struct {
594597func newResponseCapturingWriter (w http.ResponseWriter ) * responseCapturingWriter {
595598 return & responseCapturingWriter {
596599 underlying : w ,
600+ headers : make (http.Header ),
597601 statusCode : http .StatusOK ,
598602 }
599603}
600604
601- // Header returns the underlying response writer's header map so that callers
602- // can set headers which will be flushed later .
605+ // Header returns this writer's own header map so that callers can set headers
606+ // which are only forwarded to the underlying writer when flush() is called .
603607func (c * responseCapturingWriter ) Header () http.Header {
604- return c .underlying . Header ()
608+ return c .headers
605609}
606610
607611// Write captures the response body into an internal buffer.
@@ -614,12 +618,18 @@ func (c *responseCapturingWriter) WriteHeader(code int) {
614618 c .statusCode = code
615619}
616620
617- // flush sends the buffered status code, headers, and body to the underlying writer.
621+ // flush copies captured headers and sends the buffered status code and body to the underlying writer.
618622func (c * responseCapturingWriter ) flush () {
619623 if c .headerSent {
620624 return
621625 }
622626 c .headerSent = true
627+ // Copy captured headers to the underlying writer before sending the status code.
628+ for k , vals := range c .headers {
629+ for _ , v := range vals {
630+ c .underlying .Header ().Add (k , v )
631+ }
632+ }
623633 c .underlying .WriteHeader (c .statusCode )
624634 _ , _ = c .underlying .Write (c .body .Bytes ()) //nolint:gosec // G705: body is pipeline output, written back to same response
625635}
@@ -694,7 +704,7 @@ func (h *openAPIRouteHandler) validateResponse(statusCode int, headers http.Head
694704 return errs
695705 }
696706
697- if bodyErrs := validateJSONValue (bodyData , "response" , mediaType .Schema ); len (bodyErrs ) > 0 {
707+ if bodyErrs := validateJSONValue (bodyData , "response body " , mediaType .Schema ); len (bodyErrs ) > 0 {
698708 errs = append (errs , bodyErrs ... )
699709 }
700710
@@ -942,19 +952,21 @@ func validateScalarValue(val, name, kind string, schema *openAPISchema) []string
942952}
943953
944954// validateJSONBody validates a decoded JSON body against an object schema.
945- func validateJSONBody (body any , schema * openAPISchema ) []string {
955+ // The bodyLabel parameter (e.g. "request body" or "response body") is used in
956+ // error messages to distinguish validation context.
957+ func validateJSONBody (body any , schema * openAPISchema , bodyLabel string ) []string {
946958 var errs []string
947959 obj , ok := body .(map [string ]any )
948960 if ! ok {
949961 if schema .Type == "object" {
950- return []string {"request body must be a JSON object" }
962+ return []string {bodyLabel + " must be a JSON object" }
951963 }
952964 return nil
953965 }
954966 // Check required fields
955967 for _ , req := range schema .Required {
956968 if _ , present := obj [req ]; ! present {
957- errs = append (errs , fmt .Sprintf ("request body : required field %q is missing" , req ))
969+ errs = append (errs , fmt .Sprintf ("%s : required field %q is missing" , bodyLabel , req ))
958970 }
959971 }
960972 // Validate individual properties
@@ -967,6 +979,18 @@ func validateJSONBody(body any, schema *openAPISchema) []string {
967979 errs = append (errs , fieldErrs ... )
968980 }
969981 }
982+ // Validate additionalProperties: keys not declared in Properties are checked
983+ // against the additionalProperties schema when it is specified.
984+ if schema .AdditionalProperties != nil {
985+ for key , val := range obj {
986+ if _ , defined := schema .Properties [key ]; defined {
987+ continue
988+ }
989+ if fieldErrs := validateJSONValue (val , key , schema .AdditionalProperties ); len (fieldErrs ) > 0 {
990+ errs = append (errs , fieldErrs ... )
991+ }
992+ }
993+ }
970994 return errs
971995}
972996
@@ -1038,7 +1062,7 @@ func validateJSONValue(val any, name string, schema *openAPISchema) []string {
10381062 errs = append (errs , fmt .Sprintf ("field %q must be a boolean, got %T" , name , val ))
10391063 }
10401064 case "object" :
1041- if subErrs := validateJSONBody (val , schema ); len (subErrs ) > 0 {
1065+ if subErrs := validateJSONBody (val , schema , name ); len (subErrs ) > 0 {
10421066 errs = append (errs , subErrs ... )
10431067 }
10441068 case "array" :
0 commit comments