json-schema-guardrail validates a quoted JSON string when JSONPath points at message content, so object schemas always fail on OpenAI-shaped payloads
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
Research direction
Start in policies/json-schema-guardrail/jsonschemaguardrail.go at extractValueFromJSONPathForSchema, then run go test . -run TestJSONPathStringContentValidatedAsQuotedString -v in that policy directory. The issue includes the expected behavior and a regression test; done means JSON strings containing objects or arrays validate against those schemas, while plain text keeps current string validation.
Written by the indexing model from the issue text.
Description
Description
The policy default JSONPaths are $.messages[-1].content (request) and $.choices[0].message.content (response) — the OpenAI chat fields that almost always hold a JSON string. extractValueFromJSONPathForSchema in wso2/gateway-controllers policies/json-schema-guardrail/jsonschemaguardrail.go re-marshals whatever JSONPath returns:
value, err := utils.ExtractValueFromJsonpath(jsonData, jsonPath)
// ...
valueBytes, err := json.Marshal(value)
documentLoader = gojsonschema.NewBytesLoader(valueBytes)
When content is the string {"name":"alice"}, json.Marshal produces the JSON string value "{\"name\":\"alice\"}". An object schema such as {"type":"object","required":["name"],...} then fails against a string document, and the guardrail returns 422. The same schema passes when content is already a JSON object (uncommon for chat completions).
So the advertised default path cannot validate structured content that tools and users place inside the message string — the primary use case for this guardrail on LLM APIs.
Steps to reproduce
- On gateway-controllers
main(4cf9570), inpolicies/json-schema-guardrail:
func TestJSONPathStringContentValidatedAsQuotedString(t *testing.T) {
schema := `{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}`
p, err := GetPolicy(policy.PolicyMetadata{}, map[string]interface{}{
"request": map[string]interface{}{
"enabled": true,
"schema": schema,
"jsonPath": "$.messages[-1].content",
},
})
if err != nil {
t.Fatal(err)
}
// Typical OpenAI shape: content is a string that itself holds JSON.
body := []byte(`{"messages":[{"role":"user","content":"{\"name\":\"alice\"}"}]}`)
req := &policy.RequestContext{Body: &policy.Body{Content: body}}
act := p.(*JSONSchemaGuardrailPolicy).OnRequestBody(context.Background(), req, nil)
if _, ok := act.(policy.UpstreamRequestModifications); ok {
t.Fatal("stringified JSON content unexpectedly passed object schema")
}
// Same schema with content already an object passes.
body2 := []byte(`{"messages":[{"role":"user","content":{"name":"alice"}}]}`)
req2 := &policy.RequestContext{Body: &policy.Body{Content: body2}}
act2 := p.(*JSONSchemaGuardrailPolicy).OnRequestBody(context.Background(), req2, nil)
if _, ok := act2.(policy.UpstreamRequestModifications); !ok {
t.Fatalf("object content should pass, got %T", act2)
}
}
go test . -run TestJSONPathStringContentValidatedAsQuotedString -v
Expected behavior
When the JSONPath result is a string that parses as JSON, validate the parsed value (so the default OpenAI content path works with object/array schemas). If the string is not JSON, validate it as a JSON string (current behavior for plain text).
Actual behavior
Stringified JSON content is rejected with HTTP 422 JSON_SCHEMA_GUARDRAIL / "Violation of JSON schema detected." Object-typed content at the same path passes.
Environment
- gateway-controllers commit:
main@4cf9570(filed against api-platform; gateway-controllers has issues disabled) - Policy:
json-schema-guardrailv1.0.1 (policy-definition.yaml) - Go module:
github.com/wso2/gateway-controllers/policies/json-schema-guardrail - OS: Linux x86_64
Related issues / PRs
#692 is a generic "JSON Schema Guardrail Policy Issue" pointer with no reproduction of this path. #3571 is JSONPath RFC 9535 support, not string decoding. #3413 covers upstream status masking and $.messages as a list path — different. No open/closed issue describes stringified message content vs object schema.
Possible Solution
In extractValueFromJSONPathForSchema, if value is a string, attempt json.Unmarshal into a temporary interface{} and marshal that on success; otherwise keep marshaling the string as today:
if s, ok := value.(string); ok {
var parsed interface{}
if err := json.Unmarshal([]byte(s), &parsed); err == nil {
value = parsed
}
}
valueBytes, err := json.Marshal(value)
Add the regression test above (string content fails today; object content passes).
I'm happy to send a PR.
- Dominant language
- Go
- Stars
- 79
- Forks
- 130
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 157
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- No contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from wso2/api-platform
-
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
wso2/api-platform#3917 ·
Maintainers usually reply within 1 day
-
[Bug]: ap gateway image build cannot find local policies on WindowsPossibly taken @Jayzilva claimed this 2 days ago. OpenAspect/Configuration Severity/Major
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
wso2/api-platform#3912 ·
Maintainers usually reply within 1 day
-
Severity/Major
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
wso2/api-platform#3910 ·
Maintainers usually reply within 1 day
-
Aspect/API Severity/Minor
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
wso2/api-platform#3909 ·
Maintainers usually reply within 1 day
-
Aspect/API Aspect/UI Severity/Minor Type/Bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
wso2/api-platform#3904 ·
Maintainers usually reply within 1 day
All issues in wso2/api-platform
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
[flaky test] TestValidatorCommitterDropsEmptyStatusBatch: submits to the mock VC before its stream is registeredPossibly taken @liran-funaro claimed this today. Opencoordinator flaky-test race-condition testing
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
hyperledger/fabric-x-committer#876 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
garethgeorge/backrest#1426 ·
Maintainers usually reply within 1 day
-
enhancement nav-pilot
Difficulty 2/5 Half a day Newbie friendliness 62/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
valkey-io/valkey-operator#549 · 1 comment ·
Maintainers usually reply within 3 days