Skip to content

Parser: Properly resolve nonce: true for javascript_tag#1452

Merged
marcoroth merged 1 commit intomainfrom
content_security_policy_nonce
Mar 22, 2026
Merged

Parser: Properly resolve nonce: true for javascript_tag#1452
marcoroth merged 1 commit intomainfrom
content_security_policy_nonce

Conversation

@marcoroth
Copy link
Owner

@marcoroth marcoroth commented Mar 22, 2026

In Rails, nonce: true on javascript_include_tag and javascript_tag resolve to content_security_policy_nonce at runtime, and nonce: false omits the attribute. Other helpers like tag.script and content_tag pass it through as a literal value.

Previously, javascript_include_tag and javascript_tag also just passed along and transformed the nonce value as a literal value.

This pulls request adds resolve_nonce_attribute() to the parser to handle this transformation for javascript_include_tag and javascript_tag.

So a document like:

<%= javascript_tag nonce: true do %>
  alert('Hello')
<% end %>

Now properly parses as:

@ DocumentNode (location: (1:0)-(4:0))
└── children: (2 items)
    ├── @ HTMLElementNode (location: (1:0)-(3:9))
    │   ├── open_tag:
    │   │   └── @ ERBOpenTagNode (location: (1:0)-(1:36))
    │   │       ├── tag_opening: "<%=" (location: (1:0)-(1:3))
    │   │       ├── content: " javascript_tag nonce: true do " (location: (1:3)-(1:34))
    │   │       ├── tag_closing: "%>" (location: (1:34)-(1:36))
    │   │       ├── tag_name: "script" (location: (1:4)-(1:18))
    │   │       └── children: (1 item)
    │   │           └── @ HTMLAttributeNode (location: (1:19)-(1:30))
    │   │               ├── name:
    │   │               │   └── @ HTMLAttributeNameNode (location: (1:19)-(1:24))
    │   │               │       └── children: (1 item)
    │   │               │           └── @ LiteralNode (location: (1:19)-(1:24))
    │   │               │               └── content: "nonce"
    │   │               │
    │   │               ├── equals: ": " (location: (1:24)-(1:26))
    │   │               └── value:
    │   │                   └── @ HTMLAttributeValueNode (location: (1:26)-(1:30))
    │   │                       ├── open_quote: ∅
    │   │                       ├── children: (1 item)
-   │   │                       │   └── @ LiteralNode (location: (1:26)-(1:30))
-   │   │                       │       └── content: "true"
+   │   │                       │   └── @ RubyLiteralNode (location: (1:26)-(1:30))
+   │   │                       │       └── content: "content_security_policy_nonce"
    │   │                       │
    │   │                       ├── close_quote: ∅
    │   │                       └── quoted: false
    │   │
    │   ├── tag_name: "script" (location: (1:4)-(1:18))
    │   ├── body: (1 item)
    │   │   └── @ LiteralNode (location: (1:36)-(3:0))
    │   │       └── content: "\n  alert('Hello')\n"
    │   │
    │   ├── close_tag:
    │   │   └── @ ERBEndNode (location: (3:0)-(3:9))
    │   │       ├── tag_opening: "<%" (location: (3:0)-(3:2))
    │   │       ├── content: " end " (location: (3:2)-(3:7))
    │   │       └── tag_closing: "%>" (location: (3:7)-(3:9))
    │   │
    │   ├── is_void: false
    │   └── element_source: "ActionView::Helpers::JavaScriptHelper#javascript_tag"
    │
    └── @ HTMLTextNode (location: (3:9)-(4:0))
        └── content: "\n"

So that it can get properly transformed from/to:

<script nonce="<%= content_security_policy_nonce %>">
  alert('Hello')
</script>

Additionally, it updates the html-require-script-nonce linter rule to flag tag.script and content_tag :script using nonce: true or nonce: false, since those produce literal attribute values that will not match the CSP header. The rule now flags the following:

<%= tag.script nonce: true do %>
  alert('Hello')
<% end %>

with:

`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.

Follow up on #1384

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 22, 2026

npx https://pkg.pr.new/@herb-tools/formatter@1452
npx https://pkg.pr.new/@herb-tools/language-server@1452
npx https://pkg.pr.new/@herb-tools/linter@1452

commit: b30490a

@github-actions
Copy link

github-actions bot commented Mar 22, 2026

🌿 Interactive Playground and Documentation Preview

A preview deployment has been built for this pull request. Try out the changes live in the interactive playground:


🌱 Grown from commit b30490a


✅ Preview deployment has been cleaned up.

@marcoroth marcoroth added this to the v1.0.0 milestone Mar 22, 2026
@marcoroth marcoroth merged commit c234f26 into main Mar 22, 2026
32 checks passed
@marcoroth marcoroth deleted the content_security_policy_nonce branch March 22, 2026 02:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant