Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

json-schema-guardrail validates a quoted JSON string when JSONPath points at message content, so object schemas always fail on OpenAI-shaped payloads

Open Beginner friendly
#3,916 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
go
Domain
api, backend

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

  1. On gateway-controllers main (4cf9570), in policies/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)
    }
}
  1. 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-guardrail v1.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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from wso2/api-platform

All issues in wso2/api-platform

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.