Skip to content

ast: Mark JSON schema builtins nondeterministic - #9100

Open
ARMeeru wants to merge 1 commit into
open-policy-agent:mainfrom
ARMeeru:fix/json-schema-builtins-nondeterministic
Open

ast: Mark JSON schema builtins nondeterministic#9100
ARMeeru wants to merge 1 commit into
open-policy-agent:mainfrom
ARMeeru:fix/json-schema-builtins-nondeterministic

Conversation

@ARMeeru

@ARMeeru ARMeeru commented Aug 28, 2026

Copy link
Copy Markdown

Why the changes in this PR are needed?

json.match_schema and json.verify_schema dereference $ref at evaluation time, including remote and file:// URLs, so their results depend on what those locations serve. Neither is declared Nondeterministic.

Reported in #8998, where the reporter walks the builtin list and disallows nondeterministic builtins when running sandboxed code. These two pass that filter silently.

There is also a concrete consequence in partial evaluation. ignoreDuringPartial() keys off the same flag, so partial eval folds these calls when their arguments are known, fetching over the network at partial-eval time and baking the answer into the residual policy. A residual built today can encode whatever a remote server happened to serve at build time.

What are the changes in this PR?

Nondeterministic: true on both builtins in v1/ast/builtins.go, and capabilities.json regenerated with ./build/gen-run-go.sh. The regenerated file changes exactly those two entries.

A partial evaluation case in v1/topdown/topdown_partial_test.go asserts the call is left in the residual. It fails without the flags, where the residual collapses to input.x = 1 because the call was folded away.

CanSkipBctx is unchanged: both builtins need the BuiltinContext to read the allow_net capability.

Notes to assist PR review:

Measured on this branch against main, same query, with a local HTTP server counting requests:

requests residual
main 1 input.unrelated = "x", the schema call folded away
this branch 0 the json.match_schema call retained

One correction to the issue: allow_net is not bypassed. Running the same policy with allow_net: [] makes zero requests, so remote $ref fetching is already gated. Worth stating so this is not read as an unbounded SSRF.

The test's $ref points at the file's existing local test server rather than a public URL, so if the flags are ever removed the resulting fetch stays on loopback instead of leaving CI.

Worth weighing before merge: this changes partial-eval residuals for anyone using these builtins, since calls that were folded are now retained. That is the intended fix, but it is a behaviour change beyond the capabilities metadata.

capabilities/v1.20.0.json already shipped with the old values and is left untouched, so this lands in v1.21.0.

Further comments:

Fixes #8998. The reporter confirmed on the issue that marking them plus updating capabilities covers his case, and noted he does not use partial eval directly, so the partial-eval half has not had a maintainer's view yet.

That is the open question I would most like input on: is marking both builtins the change you want, or would you rather treat the partial-eval folding separately?

json.match_schema and json.verify_schema dereference `$ref` at
evaluation time, including remote and file:// URLs, so their results
depend on what those locations serve. Neither was declared
Nondeterministic.

The visible consequence is in partial evaluation. Because
ignoreDuringPartial() keys off that flag, partial eval folded both
builtins when their arguments were known, fetching over the network at
partial-eval time and baking the answer into the residual policy. With
the flag set, the calls are left in the residual and evaluated at query
time instead.

Network access itself remains gated by the allow_net capability, which
already applies to these builtins.

capabilities.json is regenerated to carry the new flag. A partial
evaluation test covers the retained call; it points at the existing
local test server so a regression fails there rather than reaching the
network.

Fixes: open-policy-agent#8998
Signed-off-by: Asifur Rahaman Meeru <asifur.rahaman@meeru.dev>
@ARMeeru
ARMeeru marked this pull request as ready for review August 29, 2026 05:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

json.{match_schema, verify_schema}() are nondeterministic but are not declared as such

1 participant