Skip to content

fix(web): escape JSON-LD to prevent </script> breakout XSS (#200 H1)#203

Merged
williamzujkowski merged 1 commit into
mainfrom
fix/jsonld-xss-escape
Jun 23, 2026
Merged

fix(web): escape JSON-LD to prevent </script> breakout XSS (#200 H1)#203
williamzujkowski merged 1 commit into
mainfrom
fix/jsonld-xss-escape

Conversation

@williamzujkowski

Copy link
Copy Markdown
Collaborator

Addresses the H1 finding in #200 (confirmed).

Problem

apps/web/src/pages/statute/[...slug].astro:147 rendered Schema.org JSON-LD via set:html={JSON.stringify(...)}. JSON.stringify does not escape < / > / &, and set:html adds no escaping. A statute title/classification (sourced from OLRC XML→markdown) containing </script><img src=x onerror=alert(1)> would close the <script type="application/ld+json"> element and inject live HTML — not blocked by the site's script-src 'unsafe-inline' CSP.

Fix

Apply the standard safe-JSON-in-<script> escaping to the serialized string:

}).replace(/</g, '\\u003c').replace(/>/g, '\\u003e').replace(/&/g, '\\u0026')} />

Verification

  • Escaping a payload Evil </script><img src=x onerror=alert(1)> → output contains no literal </script>; JSON.parse round-trips identically (still valid JSON-LD).
  • Confirmed this is the only set:html={JSON.stringify(...)} sink in the app.
  • pnpm --filter @civic-source/web build passes.

Remaining items from #200 (H2 markdown-body sanitization, M1 CSP 'unsafe-inline') are tracked separately in that issue.

🤖 Generated with Claude Code

The Schema.org Legislation JSON-LD on statute pages was rendered via
`set:html={JSON.stringify(...)}`. JSON.stringify does not escape `<`,
`>`, or `&`, and Astro's set:html adds no escaping — so a statute whose
title/classification (derived from OLRC XML) contained
`</script><img src=x onerror=...>` would terminate the JSON-LD script
element and inject live HTML.

Apply the standard safe-JSON-in-<script> escaping
(`<` / `>` / `&`) to the serialized output. Verified the
result still round-trips as valid JSON (valid JSON-LD) and no longer
contains a literal `</script>`. `astro build` passes.

Refs #200 (H1)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@williamzujkowski williamzujkowski requested a review from a team as a code owner June 23, 2026 03:39
@williamzujkowski williamzujkowski merged commit 0af8e74 into main Jun 23, 2026
3 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.

1 participant