Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Escape JSON state embedded in inline <script> tags
When injecting server state into an inline script (SSR hydration, config), use an XSS-safe serializer or data attributes; raw JSON.stringify can be broken out of with </script>.
Bad example
| 1 | function renderPage(state: AppState): string { |
| 2 | // state may contain user-controlled fields (username, bio, etc.) |
| 3 | return ` |
| 4 | <div id="root"></div> |
| 5 | <script>window.__STATE__ = ${JSON.stringify(state)}</script> |
| 6 | `; |
| 7 | } |
Explanation (EN)
Inside a <script> the HTML parser still scans for </script>. If any string in state contains </script><script>alert(1)</script>, JSON.stringify happily preserves it and the injected script runs. JSON.stringify is not an HTML/script-context encoder — it does not escape <, >, or the closing-tag sequence.
Objašnjenje (HR)
Unutar <script> HTML parser i dalje trazi </script>. Ako bilo koji string u state-u sadrzi </script><script>alert(1)</script>, JSON.stringify ga uredno ocuva i ubacena skripta se izvrsi. JSON.stringify nije enkoder za HTML/script kontekst — ne escapira <, > ni zatvarajuci tag.
Good example
| 1 | import serialize from 'serialize-javascript'; |
| 2 |
|
| 3 | function renderPage(state: AppState): string { |
| 4 | // serialize-javascript escapes <, >, /, line separators and the </script> sequence |
| 5 | return ` |
| 6 | <div id="root"></div> |
| 7 | <script>window.__STATE__ = ${serialize(state, { isJSON: true })}</script> |
| 8 | `; |
| 9 | } |
| 10 |
|
| 11 | // Or avoid the script context entirely — embed JSON in a typed script block and parse it: |
| 12 | // <script id="state" type="application/json">{escaped json}</script> |
| 13 | // const state = JSON.parse(document.getElementById('state').textContent); |
Explanation (EN)
serialize-javascript (or escaping <, >, & and the </script> sequence by hand) keeps the data from terminating the script element. An even cleaner option is a <script type="application/json"> block read via textContent + JSON.parse, which the browser never executes. Frameworks like Next.js already do this safely; only hand-rolled SSR needs the fix.
Objašnjenje (HR)
serialize-javascript (ili rucno escapanje <, >, & i </script> sekvence) sprjecava podatke da zatvore script element. Jos ciscija opcija je <script type="application/json"> blok citan preko textContent + JSON.parse, koji preglednik nikad ne izvrsava. Frameworci poput Next.js ovo vec rade sigurno; popravak treba samo rucno pisani SSR.
Notes (EN)
Covered by the OWASP XSS Prevention Cheat Sheet rule on JavaScript and JSON contexts; the </script> and U+2028/U+2029 cases are the usual breakouts.
Bilješke (HR)
Pokriveno OWASP XSS Prevention Cheat Sheet pravilom o JavaScript i JSON kontekstima; </script> i U+2028/U+2029 su uobicajeni nacini izlaska.
Exceptions / Tradeoffs (EN)
Using a framework's built-in hydration mechanism (Next.js __NEXT_DATA__, Remix) is already safe — this rule targets hand-written inline script injection. Numeric/boolean-only state without strings is lower risk but still better serialized safely.
Iznimke / Tradeoffi (HR)
Koristenje ugradenog hydration mehanizma frameworka (Next.js __NEXT_DATA__, Remix) je vec sigurno — ovo pravilo cilja rucno pisanu injekciju inline skripte. State sa samo brojevima/booleanima bez stringova je manji rizik, ali ga je i dalje bolje sigurno serijalizirati.