Visitar URL original
fix(unhead): escape control characters in JSON template parameters by henricop · Pull Request #1039 · unjs/unhead · GitHub
Skip to content

fix(unhead): escape control characters in JSON template parameters - #1039

Merged
harlan-zw merged 1 commit into
unjs:mainfrom
henricop:fix/json-template-control-characters
Nov 7, 2026
Merged

harlan-zw merged 1 commit into
unjs:mainfrom
henricop:fix/json-template-control-characters

Conversation

@henricop

@henricop henricop commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

🔗 Linked issue

Resolves #1038

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

An opted-in JSON or JSON-LD script becomes invalid JSON when a template parameter contains a newline, tab, or another U+0000–U+001F character. The current replacement only escapes quotes, backslashes, and <.

Use JSON.stringify to escape the replacement string, strip its enclosing quotes, and retain the existing < escape. This preserves string contents when parsed and keeps a parameter containing </script> from closing the script element. Non-JSON replacements retain their current behavior.

The regression tests render both script MIME types, covering all 32 control characters together with quotes, a backslash, and a closing-script sequence. All 64 cases fail with the original implementation and pass with this change. A plain-text control checks that newlines remain literal outside JSON.

Validation on macOS arm64, Node 24.19.0 and pnpm 12.4.1:

  • Focused template parameter suite: 71 passed.
  • pnpm lint: passed, with 12 warnings in existing files; changed-file ESLint check also passed.
  • pnpm build, pnpm typecheck (CI heap setting), and pnpm test:attw: passed.
  • CI=true TZ=UTC pnpm exec vitest run --maxWorkers=1: 2,563 passed, 8 skipped; includes export snapshots and the Nuxt Cloudflare fixture build.
  • pnpm test:import-inert: all 49 entries passed.
  • All four bundle-size scripts passed. The five tracked core bundles have identical raw/gzip sizes against a clean build of the base commit.
  • Built utils entry: all 32 control characters round-trip with the fix; all 32 produce invalid JSON on the base commit.

Earlier parallel test runs encountered missing build artifacts and temporary-file ENOENT errors; the final sequential run above passed after the build and fixture dependencies were complete. The initial local timezone also affected existing date snapshots; the final run uses UTC as in CI.

AI assistance: Codex helped implement the change and run the checks described above.

Summary by CodeRabbit

  • Bug Fixes
    • Improved JSON template-value escaping in server-rendered scripts, preserving special characters while preventing embedded </script> text from breaking the script.
    • Preserved newline characters in plain-text template substitutions.

@coderabbitai

coderabbitai Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

JSON template substitutions now use JSON string escaping and escape less-than characters. Tests check SSR output for JSON script types and confirm that plain-text substitutions preserve newlines.

Changes: Template parameter escaping

Layer / File(s) Summary
JSON escaping and regression tests
packages/unhead/src/utils/templateParams.ts, packages/unhead/test/unit/server/templateParams.test.ts
JSON substitutions use JSON.stringify and replace < with \u003C. SSR tests check control-character round trips and ensure rendered script bodies contain no literal </script>. A plain-text test checks that newline characters remain unchanged.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: harlan-zw

Merge Risk: Minimal · up to ab8d0

JSON substitutions escape control characters while plain-text behavior remains unchanged. No merge-blocking issue was identified.

Pre-merge checks: Passed 5

Check name Status Explanation
Title check Passed The title clearly identifies the JSON template parameter escaping fix.
Description check Passed The description completes the required sections and explains the issue, fix, and regression tests.
Linked Issues check Passed [#1038] The helper uses JSON string escaping and retains the < escape. SSR tests cover both JSON script MIME types, all 32 control characters, quotes, backslashes, and </script>. A plain-text test…
Out of Scope Changes check Passed [#1038] The helper change and regression tests address the linked JSON escaping issue. The plain-text test checks that unrelated substitution behavior remains unchanged.
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Full details: Linked Issues check

Explanation

[#1038] The helper uses JSON string escaping and retains the &lt; escape. SSR tests cover both JSON script MIME types, all 32 control characters, quotes, backslashes, and &lt;/script&gt;. A plain-text test checks that newlines remain unchanged.

Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 1 functions across 2 files.

Finishing Touches

Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7c94a3e6-a4ef-4e83-942f-9ea25e9edc3c
Commits

Reviewing files that changed from the base of the PR and between 4d655bf and ab8d0da.

Files selected for processing: 2

  • packages/unhead/src/utils/templateParams.ts
  • packages/unhead/test/unit/server/templateParams.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

@harlan-zw

harlan-zw commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

🤖 MERGED

Harlan Agent Kit posted this automated review. It is not Harlan's personal review or approval. AI open source policy. Last updated: 2026-11-07 10:42 UTC.

GitHub merged this pull request.

No material findings were recorded.

2af853af-8868-4342-8519-fde63c795a9d

@harlan-zw harlan-zw added harlan-agent-review Approve automated review and verified repairs for the current PR commit harlan-agent-review-required Pull request triage requires an adversarial Review for this head commit. harlan-agent-running An Agent holds a Task on this issue or pull request right now. harlan-agent-ready The automated Review passed every gate on this head commit. and removed harlan-agent-review Approve automated review and verified repairs for the current PR commit harlan-agent-running An Agent holds a Task on this issue or pull request right now. harlan-agent-review-required Pull request triage requires an adversarial Review for this head commit. labels Nov 7, 2026
@harlan-zw

Copy link
Copy Markdown
Collaborator

Great catch, thanks!

@harlan-zw
harlan-zw merged commit f3ab163 into unjs:main Nov 7, 2026
6 checks passed
@harlan-zw harlan-zw removed the harlan-agent-ready The automated Review passed every gate on this head commit. label Nov 7, 2026
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 template parameters with control characters produce invalid script JSON

2 participants