Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@

## Description

Restricts which `type` attribute values are permitted on `<script>` tags. Only approved types are allowed: `text/javascript`. An empty or valueless `type` attribute is reported.
Restricts which `type` attribute values are permitted on `<script>` tags. Only approved types are allowed: `text/javascript`, `module`, `importmap`, `speculationrules`, and `application/ld+json`. An empty or valueless `type` attribute is reported.

## Rationale

Developers frequently use `<script>` tags with non-executable type attributes (like `application/json` or `text/html`) to embed data in pages. However, these tags share parsing quirks with executable scripts and can create security risks. For example, unescaped `</script><script>` sequences inside a `text/html` script tag could enable XSS attacks.

By restricting the allowed `type` values and requiring the `type` attribute to be present, this rule helps catch typos and discourages unsafe or unintended script usage patterns.

An exception is made for `application/ld+json`, which the HTML specification treats as an inert [data block](https://html.spec.whatwg.org/multipage/scripting.html#data-block): it is the standard mechanism for embedding JSON-LD structured data, and consumers such as search engines only recognize it inside a `<script>` element.

## Examples

### ✅ Good
Expand All @@ -28,6 +30,16 @@ By restricting the allowed `type` values and requiring the `type` attribute to b
</script>
```

```erb
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Example"
}
</script>
```

### 🚫 Bad

```erb
Expand All @@ -42,6 +54,12 @@ By restricting the allowed `type` values and requiring the `type` attribute to b
</script>
```

```erb
<script type="application/json">
{ "name": "Example" }
</script>
```

```erb
<script type="">
console.log("Hello")
Expand All @@ -57,3 +75,4 @@ By restricting the allowed `type` values and requiring the `type` attribute to b
## References

- [Inspiration: ERB Lint `AllowedScriptType` rule](https://github.com/Shopify/erb_lint/tree/main?tab=readme-ov-file#allowedscripttype)
- [HTML specification: data blocks](https://html.spec.whatwg.org/multipage/scripting.html#data-block)
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { HTMLAttributeNode, HTMLOpenTagNode, ParseResult } from "@herb-tool
// NOTE: Rules are not configurable for now, keep some sane defaults
// See https://github.com/marcoroth/herb/issues/1204
const ALLOW_BLANK = true
const ALLOWED_TYPES = ["text/javascript", "module", "importmap", "speculationrules"]
const ALLOWED_TYPES = ["text/javascript", "module", "importmap", "speculationrules", "application/ld+json"]

class AllowedScriptTypeVisitor extends BaseRuleVisitor {
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,16 @@ describe("html-allowed-script-type", () => {
expectNoOffenses('<script type="speculationrules"></script>')
})

test("passes when type is application/ld+json", () => {
expectNoOffenses('<script type="application/ld+json"></script>')
})

test("passes for script tag with ERB in type attribute", () => {
expectNoOffenses('<script type="<%= script_type %>"></script>')
})

test("fails when type is not allowed", () => {
expectError('Avoid using `text/yavascript` as the `type` attribute for the `<script>` tag. Must be one of: `text/javascript`, `module`, `importmap`, `speculationrules` or blank.')
expectError('Avoid using `text/yavascript` as the `type` attribute for the `<script>` tag. Must be one of: `text/javascript`, `module`, `importmap`, `speculationrules`, `application/ld+json` or blank.')

assertOffenses('<script type="text/yavascript"></script>')
})
Expand Down
Loading