Sheath rule
blade-prefer-json-in-script
@json(...) or Js::from(...); {{ }} HTML-entity encodes the data and corrupts it for JS.
#Why
{{ $data }} runs through e(), and e() speaks HTML: it turns " into
", & into &, < into <. Inside a <script> those
entities are never decoded. JavaScript reads them as literal characters:
$name = 'O"Brien & Sons';
<script>var name = {{ $name }};</script>
-> var name = O"Brien & Sons; <- not JS, not the data
Laravel ships the right tools: @json($data) and {{ Js::from($data) }}
emit properly JS-escaped output (Js::from returns an Htmlable, which
e() passes through untouched).
JSON-only script types need JSON text rather than a JavaScript expression.
Use @json($data) there. Js::from() may emit a JSON.parse(...) expression,
which is valid in executable JavaScript but is not itself JSON. Although
Js::encode() produces JSON text, it returns a plain string, so wrapping it in
{{ }} makes Blade entity-escape the quotes and corrupt the JSON.
The rule reports escaped Blade echoes anywhere inside executable script data.
In a JavaScript value position it recommends direct JSON serialization. Inside
a string literal, template literal, or comment it instead tells you to move the
expression to a value position first: HTML escaping can corrupt a string and
characters such as */ can terminate a comment. Script blocks whose type
marks them as HTML templates (text/x-template, text/html) are exempt because
entity encoding is correct in HTML content. JSON script blocks
(type="application/json") are checked: entities corrupt them the same way.
Raw echoes can expose untrusted content without escaping. See
security-no-raw-echo.
#Examples
#Bad
<script>
var data = {{ $data }};
</script>
<script>
const config = {
items: {{ $items }},
};
</script>
<script type="application/json">
{"items": {{ $items }}}
</script>
<script type="application/ld+json">
{{ Js::from($schema) }}
</script>
<script>
var name = '{{ $name }}';
</script>
<script>
/* {{ $note }} */
</script>
#Good
<script>
var data = @json($data);
</script>
<script>
const config = {
items: {{ Js::from($items) }},
};
</script>
<script type="application/ld+json">
@json($schema)
</script>
{{-- HTML template blocks are HTML context, not JS --}}
<script type="text/x-template">
<p>{{ $name }}</p>
</script>
#Notes
- The
strictpreset enables this check. Numeric output such asvar count = {{ $count }}may happen to work, so projects using that pattern can otherwise receive many new findings. - Prefer
@json($data)for a value dropped straight into JS, and{{ Js::from($data) }}when you want an expression form. Both produce output that is safe against</script>breakout as well. - In
application/json,application/ld+json,importmap, andspeculationrulesblocks, use@json(...); those blocks are parsed as JSON and cannot containJSON.parse(...)or HTML-escaped JSON text. - Echoes in ambiguous positions are not reported.
- Standard JavaScript MIME essence strings, including legacy values, are recognized case-insensitively after trimming ASCII whitespace, as required by HTML's MIME-type processing.
#Related Rules
- security-no-raw-echo - raw echoes, including in scripts
- security-no-inline-js - inline script blocks generally