Skip to content

Linter Rule: Require nonce attribute on inline scripts

Rule: html-require-script-nonce

Description

Require a nonce attribute on inline <script> tags and script-producing Rails helpers. This helps enforce a Content Security Policy (CSP) that mitigates cross-site scripting (XSS) attacks.

External scripts are not flagged. That covers any element with a src attribute, including the ones generated by javascript_include_tag.

Rationale

A Content Security Policy with a nonce-based approach ensures that only scripts with a valid, server-generated nonce are executed by the browser. Without a nonce, inline scripts may be blocked by CSP, or worse, CSP may need to be relaxed with unsafe-inline, defeating its purpose. External scripts are instead controlled by the CSP script-src source list.

Adding nonces to inline scripts ensures:

  • Scripts are allowed by the CSP without weakening it
  • Protection against XSS attacks that attempt to inject unauthorized scripts
  • Consistent security practices across the codebase

Examples

✅ Good

HTML script tags with a nonce:

erb
<script nonce="<%= request.content_security_policy_nonce %>">
  alert("Hello, world!")
</script>
erb
<script type="text/javascript" nonce="<%= request.content_security_policy_nonce %>">
  console.log("Hello")
</script>

Rails helpers with nonce: true:

erb
<%= javascript_tag nonce: true do %>
Avoid `javascript_tag`. Use inline `<script>` tags instead. (erb-no-javascript-tag-helper)
alert("Hello, world!") <% end %>

Unlike javascript_tag, tag.script does not resolve nonce: true to the request's CSP nonce. Pass the nonce value explicitly:

erb
<%= tag.script nonce: request.content_security_policy_nonce do %>
  alert("Hello, world!")
<% end %>

External scripts (not flagged):

erb
<script src="/assets/application.js"></script>
erb
<%= javascript_include_tag "application" %>

Non-JavaScript script types (not flagged):

erb
<script type="application/json">
Avoid using `application/json` as the `type` attribute for the `<script>` tag. Must be one of: `text/javascript`, `module`, `importmap`, `speculationrules`, `application/ld+json` or blank. (html-allowed-script-type)
{"key": "value"} </script>
erb
<script type="application/ld+json">
  {"@context": "https://schema.org"}
</script>

🚫 Bad

HTML script tags without a nonce:

erb
<script>
Missing a `nonce` attribute on `<script>` tag. Use `request.content_security_policy_nonce`. (html-require-script-nonce)
alert("Hello, world!") </script>
erb
<script type="text/javascript">
Missing a `nonce` attribute on `<script>` tag. Use `request.content_security_policy_nonce`. (html-require-script-nonce)
console.log("Hello") </script>

Inline Rails helpers without a nonce:

erb
<%= javascript_tag do %>
Missing a `nonce` attribute on `<script>` tag. Use `request.content_security_policy_nonce`. (html-require-script-nonce)
Avoid `javascript_tag`. Use inline `<script>` tags instead. (erb-no-javascript-tag-helper)
alert("Hello, world!") <% end %>
erb
<%= tag.script do %>
Missing a `nonce` attribute on `<script>` tag. Use `request.content_security_policy_nonce`. (html-require-script-nonce)
alert("Hello, world!") <% end %>

Rails tag helpers with a literal boolean nonce:

erb
<%= tag.script nonce: true do %>
`nonce: true` on `tag.script` outputs a literal `nonce="true"` attribute, which will not match the Content Security Policy header and the browser will block the script. Only `javascript_tag` and `javascript_include_tag` resolve `nonce: true` to the per-request `content_security_policy_nonce`. Use `javascript_tag` with `nonce: true` instead. (html-require-script-nonce)
alert("Hello, world!") <% end %>

Framework-specific recommendations

The offense for a missing nonce names request.content_security_policy_nonce only when the project opts into Action View in its .herb.yml:

yaml
framework: actionview

Under any other framework, and when no framework is configured, the rule recommends a dynamically generated nonce without naming a Rails API.

References

Released under the MIT License.