Skip to content

script_safe: fix escaping of ", \ and control characters - #1146

Merged
byroot merged 1 commit into
ruby:masterfrom
nagachika:fix-script-safe-escape-regression
Nov 14, 2026
Merged

byroot merged 1 commit into
ruby:masterfrom
nagachika:fix-script-safe-escape-regression

Conversation

@nagachika

Copy link
Copy Markdown
Member

889c06a (#1145) changed search_script_safe_escape to return the raw table value (ESCAPE_LEN_1) and leave flushing to the escape function. full_escape_UTF8_char (the ascii_only path) was updated for this, but escape_UTF8_char (the script_safe-only path) still matches case 1 and never calls search_flush.

As a result, with script_safe: true and without ascii_only, characters that need escaping (", \, control characters) match no case. They are dropped, and so is any pending unescaped text before them:

JSON.generate(["\""], script_safe: true)       # => "[\"\"]"     (expected "[\"\\\"\"]")
JSON.generate("a\"b\\c\nd", script_safe: true) # => "\"d\""

Fix

In escape_UTF8_char, match ESCAPE_LEN_1 and flush before appending the escape sequence, the same way full_escape_UTF8_char does.

Tests

run_script_safe_test now also covers strings with ", \, \n, \t and a control character, and a string that mixes regular escapes with a </script escape. It runs with ascii_only both off and on. The existing tests had no script_safe-only case with such characters, which is why the regression went unnoticed.

I ran the full test suite against the C extension: 702 tests, 0 failures. I also round-trip fuzzed 20,000 random strings built from <, /, script, !-, quotes, backslashes, newlines, control and multibyte characters, with ascii_only both off and on. Every output parsed back to the original string and contained no <script, </script or <!--. I have not run the tests on JRuby.

🤖 Generated with Claude Code

Since 889c06a, `search_script_safe_escape` returns the raw table
value (`ESCAPE_LEN_1`) and leaves flushing to the escape function, but
`escape_UTF8_char` still matched `case 1` and never flushed. With
`script_safe: true` (without `ascii_only`), escaped characters hit no
case, and the cursor skipped over both them and any pending unescaped
bytes, silently dropping data:

    JSON.generate(["\""], script_safe: true)       # => "[\"\"]"
    JSON.generate("a\"b\\c\nd", script_safe: true) # => "\"d\""

Match `ESCAPE_LEN_1` and flush before appending the escape, like
`full_escape_UTF8_char` does.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@nagachika
nagachika requested a review from byroot November 14, 2026 12:09
@byroot
byroot merged commit c6f5858 into ruby:master Nov 14, 2026
41 of 42 checks passed
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.

2 participants