Repository navigation
Escape <, >, &, U+2028 and U+2029 in the JSON-LD script - #44
Merged
Merged
Conversation
JsonLd.astro wrote JSON.stringify(data) raw into <script type="application/ld+json">. A string holding </script> ended the element early and put the rest of the JSON into the page as HTML, and <!--<script> made the element swallow the rest of the page. Article titles and excerpts come from the content repository, so an article could trigger it. serializeJsonLd (src/lib/json-ld.mjs) writes those characters as \u escapes. The JSON parses to the same value, so search engines read the same data. tests/json-ld.test.mjs covers it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013MdjUcGUV9BPmxbz46Ac8n
|
Preview: https://pr-44-meaningfool.josselin-perrus.workers.dev Built from 16fd183 with the latest content. The link stays the same and shows the latest push. Search engines are asked not to index it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes a latent bug in
src/components/JsonLd.astro. No current article triggers it in a way a reader can see. This is a technical change: the rendered site is unchanged, apart from one escaped character inside one page's JSON-LD (details below).Based on
mainat e8c17cc. That commit is the merge of #43 (the redesign), so this targetsmain.The bug
The component wrote
JSON.stringify(data)raw into<script type="application/ld+json">withset:html.JSON.stringifydoesn't escape<,>or&. ThearticleSchemaon/articles/<slug>/holds the title and excerpt from meaningfool-writing, so article text could break the page:</script>: the browser ends the script there. The JSON is cut off, and the rest of it lands in the page as HTML.<!--<script>: this is worse. The script element swallows the rest of the page, including<main>and the footer, up to the next</script>.The fix
src/lib/json-ld.mjs: newserializeJsonLd(data). It runsJSON.stringify(data), then writes<,>,&, U+2028 and U+2029 as the JSON escapes\u003c,\u003e,\u0026,\u2028and\u2029. The escaped text parses to the same JSON value, so search engines read the same data.src/components/JsonLd.astro: usesserializeJsonLd(data)instead ofJSON.stringify(data).tests/json-ld.test.mjs: a test for it.bun run test:documentsruns it, since that script runstests/*.test.mjs.The function lives in
src/lib/rather than inside the component so the existingnode --testsetup can import it;node --testcan't load.astrofiles.Validation
Head commit: 16fd183
Setup:
git submodule update --init --remote, which gave meaningfool-writing at bf783babun installBuild and tests
bun run build: passes, 62 pages.bun run test:documents: passes, 12 tests and 0 failures (the 10 existing ones plus the 2 new ones).serializeJsonLdis swapped back to plainJSON.stringify, so it catches the bug.buildcheck passed on 16fd183, and the preview is at https://pr-44-meaningfool.josselin-perrus.workers.dev.dist/before and after, same content (bf783ba)Two builds of unchanged
maindiffer only in theGenerated:timestamp ofllms.txtandllms-full.txt, so that is the noise floor. A script compared the before and after builds file by file, parsing the HTML with parse5:llms.txtandllms-full.txtdiffer only in theGenerated:timestamp.application/ld+jsonscripts is byte-identical./articles/structured-extraction/. Its description holds "this<500ms", which the JSON-LD now writes asthis \u003c500ms, and that is the only change in it. No other script held one of the escaped characters.Hostile titles, end to end
I added two temporary articles to the submodule's working tree (never committed, and removed afterwards). I built them with the old component and with the new one, then loaded each page in Chromium (JavaScript off) and with parse5:
JSON-LD probe </script><b>x</b>, with a description holding&, U+2028 and U+2029"headline":"JSON-LD probe, fails to parse, and the page gets a real<b>element<b>elementJSON-LD comment probe <!--<script><main>and the footer, and the JSON fails to parse<main>and the footer in placeAfter removing the probes, a last build at the head commit matched the validated "after" output, apart from the
Generated:timestamps.Not done
maindeploys the live site, so this waits for the owner's go-ahead.🤖 Generated with Claude Code
https://claude.ai/code/session_013MdjUcGUV9BPmxbz46Ac8n