Skip to content

Reject unpaired JSON surrogate escapes - #1085

Draft
He-Pin wants to merge 1 commit into
databricks:masterfrom
He-Pin:fix/parsejson-surrogates
Draft

Reject unpaired JSON surrogate escapes#1085
He-Pin wants to merge 1 commit into
databricks:masterfrom
He-Pin:fix/parsejson-surrogates

Conversation

@He-Pin

@He-Pin He-Pin commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Motivation

std.parseJson and the strict .json import fast path could let JSON strings containing lone UTF-16 surrogate halves through as Jsonnet strings. The proper fix belongs in ujson upstream; this PR adds a downstream guard.

Modification

  • Add ValVisitor.rejectUnpairedSurrogates for decoded JSON string values and object keys.
  • Enable for std.parseJson and the strict .json import path.
  • Preserve valid surrogate pairs and escaped literal text ("\\uD800").
  • Add regression coverage for lone high/low surrogates, surrogate keys, valid pairs, and escaped literals.

Result

Case before after cpp-jsonnet go-jsonnet jrsonnet
std.parseJson("\"\\uD800\"") 55296 error error 65533 error
std.parseJson("\"\\uDC00\"") 56320 error error 65533 error
std.parseJson("\"\\uD83D\\uDE00\"") (valid pair) 128512 128512 128512 128512 128512
surrogate object key 55296 error error 65533 error

Aligns with cpp-jsonnet and jrsonnet. go-jsonnet normalizes to U+FFFD (see google/go-jsonnet#885).

References

@He-Pin
He-Pin force-pushed the fix/parsejson-surrogates branch from 6aee07c to 8974a1e Compare July 4, 2026 11:52
Motivation:
std.parseJson and strict .json import fast paths accepted or normalized JSON strings containing unpaired UTF-16 surrogate escapes. That allowed invalid JSON Unicode to become Jsonnet strings, diverging from cpp-jsonnet and jrsonnet behavior and from the intent of rejecting malformed JSON input.

Modification:
Add a shared surrogate validator for JSON visitors, enable it for std.parseJson, and convert strict .json import surrogate failures into normal Jsonnet errors instead of falling back or producing raw invalid strings. Keep the import fast path allocation-conscious with a private nullable return contract, leaving any reusable OptionVal abstraction for a separate PR.

Result:
Lone high/low surrogate values and keys now fail with Invalid JSON: unpaired surrogate in string, valid surrogate pairs still materialize as codepoint strings, escaped literal text such as \\uD800 remains ordinary text, and loose Jsonnet .json files can still fall back through the Jsonnet parser.

References:
https://jsonnet.org/ref/stdlib.html#std-parseJson
google/go-jsonnet#885
com-lihaoyi/upickle#722
@He-Pin
He-Pin force-pushed the fix/parsejson-surrogates branch from 8974a1e to 07ed891 Compare July 4, 2026 11:57
@He-Pin

He-Pin commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

refs: com-lihaoyi/upickle#723

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.

1 participant