Repository: getkin/kin-openapi (Go)

Issue:
Make SchemaError.Reason field safe to log
### Background:

In regulatory environments, it is important to be mindful of the information that is logged and returned to users, as it may contain sensitive information such as private details, payment information, or secrets.

When a `SchemaError` occurs, the default error message produced by the `Error()` method includes the `Value` field. While it is possible to customize the error message using the `WithCustomSchemaErrorFunc` function, this requires the user to manually handle each possible schema validation error to produce a meaningful message.

### Suggested Solution:

I suggest making the `SchemaError.Reason` field "safe" by guaranteeing that it will not contain the original `Value`. This will allow users to simply return `SchemaError.Reason` in their custom error function, rather than having to craft the entire message for each possible scenario.

Upon review, most messages in the `Reason` field are already "safe" and do not include the original `Value`. However, there are a few messages that should be revised to remove the `Value`:

```go
"value \"%g\" must be an integer" => "value must be an integer"
`string %q doesn't match the regular expression "%s"` => `string doesn't match the regular expression %q`
"value %q is not one of the allowed values" => "value must be one of the allowed values %v"
"value of discriminator property %q is not a string: %v" => "value of discriminator property %q is not a string"
"discriminator property %q has invalid value: %q" => "discriminator property %q has invalid value"
```




Fix (patch):
diff --git a/README.md b/README.md
index 27d6699cf..b85af9864 100644
--- a/README.md
+++ b/README.md
@@ -199,6 +199,74 @@ func arrayUniqueItemsChecker(items []interface{}) bool {
 }
 ```
 
+## Custom function to change schema error messages
+
+By default, the error message returned when validating a value includes the error reason, the schema, and the input value.
+
+For example, given the following schema:
+
+```json
+{
+  "type": "string",
+  "allOf": [
+    { "pattern": "[A-Z]" },
+    { "pattern": "[a-z]" },
+    { "pattern": "[0-9]" },
+    { "pattern": "[!@#$%^&*()_+=-?~]" }
+  ]
+}
+```
+
+Passing the input value `"secret"` to this schema will produce the following error message:
+
+```
+string doesn't match the regular expression "[A-Z]"
+Schema:
+  {
+    "pattern": "[A-Z]"
+  }
+
+Value:
+  "secret"
+```
+
+Including the original value in the error message can be helpful for debugging, but it may not be appropriate for sensitive information such as secrets.
+
+To disable the extra details in the schema error message, you can set the `openapi3.SchemaErrorDetailsDisabled` option to `true`:
+
+```go
+func main() {
+	// ...
+
+	// Disable schema error detailed error messages
+	openapi3.SchemaErrorDetailsDisabled = true
+
+	// ... other validate codes
+}
+```
+
+This will shorten the error message to present only the reason:
+
+```
+string doesn't match the regular expression "[A-Z]"
+```
+
+For more fine-grained control over the error message, you can pass a custom `openapi3filter.Options` object to `openapi3filter.RequestValidationInput` that includes a `openapi3filter.CustomSchemaErrorFunc`.
+
+```go
+func validationOptions() *openapi3filter.Options {
+	options := openapi3filter.DefaultOptions
+	options.WithCustomSchemaErrorFunc(safeErrorMessage)
+	return options
+}
+
+func safeErrorMessage(err *openapi3.SchemaError) string {
+	return err.Reason
+}
+```
+
+This will change the schema validation errors to return only the `Reason` field, which is guaranteed to not include the original value.
+
 ## Sub-v0 breaking API changes
 
 ### v0.113.0
diff --git a/openapi3/schema.go b/openapi3/schema.go
index 9d21c00e6..df76c00e4 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -1125,7 +1125,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 			Value:                 value,
 			Schema:                schema,
 			SchemaField:           "enum",
-			Reason:                fmt.Sprintf("value %q is not one of the allowed values", value),
+			Reason:                fmt.Sprintf("value is not one of the allowed values %q", schema.Enum),
 			customizeMessageError: settings.customizeMessageError,
 		}
 	}
@@ -1164,16 +1164,11 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 
 				discriminatorValString, okcheck := discriminatorVal.(string)
 				if !okcheck {
-					valStr := "null"
-					if discriminatorVal != nil {
-						valStr = fmt.Sprintf("%v", discriminatorVal)
-					}
-
 					return &SchemaError{
 						Value:       discriminatorVal,
 						Schema:      schema,
 						SchemaField: "discriminator",
-						Reason:      fmt.Sprintf("value of discriminator property %q is not a string: %v", pn, valStr),
+						Reason:      fmt.Sprintf("value of discriminator property %q is not a string", pn),
 					}
 				}
 
@@ -1182,7 +1177,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 						Value:       discriminatorVal,
 						Schema:      schema,
 						SchemaField: "discriminator",
-						Reason:      fmt.Sprintf("discriminator property %q has invalid value: %q", pn, discriminatorVal),
+						Reason:      fmt.Sprintf("discriminator property %q has invalid value", pn),
 					}
 				}
 			}
@@ -1351,7 +1346,7 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 				Value:                 value,
 				Schema:                schema,
 				SchemaField:           "type",
-				Reason:                fmt.Sprintf("value \"%g\" must be an integer", value),
+				Reason:                fmt.Sprintf("value must be an integer"),
 				customizeMessageError: settings.customizeMessageError,
 			}
 			if !settings.multiError {
@@ -1571,7 +1566,7 @@ func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value
 			Value:                 value,
 			Schema:                schema,
 			SchemaField:           "pattern",
-			Reason:                fmt.Sprintf(`string %q doesn't match the regular expression "%s"`, value, schema.Pattern),
+			Reason:                fmt.Sprintf(`string doesn't match the regular expression "%s"`, schema.Pattern),
 			customizeMessageError: settings.customizeMessageError,
 		}
 		if !settings.multiError {
@@ -1932,10 +1927,12 @@ func (schema *Schema) compilePattern() (err error) {
 }
 
 type SchemaError struct {
-	Value                 interface{}
-	reversePath           []string
-	Schema                *Schema
-	SchemaField           string
+	Value       interface{}
+	reversePath []string
+	Schema      *Schema
+	SchemaField string
+	// Reason is a human-readable message describing the error.
+	// The message should never include the original value to prevent leakage of potentially sensitive inputs in error messages.
 	Reason                string
 	Origin                error
 	customizeMessageError func(err *SchemaError) string