Skip to content

Commit 7637ddf

Browse files
authored
fix: skip non-resource link tags in SRI plugin (#10829)
* fix: skip non-resource link tags in SRI plugin * fix: update Rust side and add test case * fix: clippy
1 parent 64af2e3 commit 7637ddf

File tree

4 files changed

+128
-6
lines changed

4 files changed

+128
-6
lines changed

crates/rspack_plugin_sri/src/html.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,35 @@ async fn process_tag_group(
117117
Ok(())
118118
}
119119

120+
// Get the `src` or `href` attribute of a tag if it is a script
121+
// or link tag that needs SRI.
122+
fn get_tag_src(tag: &HtmlPluginTag) -> Option<String> {
123+
// Handle script tags with src attribute
124+
if tag.tag_name == "script" {
125+
return get_tag_attribute(tag, "src");
126+
}
127+
128+
// Handle link tags that need SRI
129+
if tag.tag_name == "link" {
130+
let href = get_tag_attribute(tag, "href")?;
131+
let rel = get_tag_attribute(tag, "rel")?;
132+
133+
// Only process link tags that load actual resources
134+
let needs_sri = rel == "stylesheet"
135+
|| rel == "modulepreload"
136+
|| (rel == "preload" && {
137+
let as_attr = get_tag_attribute(tag, "as");
138+
as_attr.as_deref() == Some("script") || as_attr.as_deref() == Some("style")
139+
});
140+
141+
if needs_sri {
142+
return Some(href);
143+
}
144+
}
145+
146+
None
147+
}
148+
120149
async fn process_tag(
121150
tag: &HtmlPluginTag,
122151
public_path: &str,
@@ -133,7 +162,7 @@ async fn process_tag(
133162
return Ok(None);
134163
}
135164

136-
let Some(tag_src) = get_tag_attribute(tag, "href").or(get_tag_attribute(tag, "src")) else {
165+
let Some(tag_src) = get_tag_src(tag) else {
137166
return Ok(None);
138167
};
139168

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
it("should not process link tags that are not modulepreload, preload, or stylesheet", () => {
5+
const htmlPath = path.join(__dirname, "./index.html");
6+
const htmlContent = fs.readFileSync(htmlPath, "utf-8");
7+
expect(htmlContent).toContain('<script crossorigin defer integrity');
8+
expect(htmlContent).toContain('<link href="https://example.com" rel="dns-prefetch">');
9+
expect(htmlContent).toContain('<link href="https://example.com" rel="preconnect">');
10+
expect(htmlContent).toContain('<link href="https://example.com" rel="prefetch">');
11+
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
const { rspack } = require("@rspack/core");
2+
3+
class AddLinksPlugin {
4+
apply(compiler) {
5+
compiler.hooks.compilation.tap("AddLinksPlugin", compilation => {
6+
rspack.HtmlRspackPlugin.getCompilationHooks(
7+
compilation
8+
).alterAssetTagGroups.tapPromise("AddLinksPlugin", async data => {
9+
data.headTags.push(
10+
{
11+
tagName: "link",
12+
attributes: {
13+
href: "https://example.com",
14+
rel: "dns-prefetch"
15+
},
16+
voidTag: true
17+
},
18+
{
19+
tagName: "link",
20+
attributes: {
21+
href: "https://example.com",
22+
rel: "preconnect"
23+
},
24+
voidTag: true
25+
},
26+
{
27+
tagName: "link",
28+
attributes: {
29+
rel: "prefetch",
30+
href: "https://example.com"
31+
},
32+
voidTag: true
33+
}
34+
);
35+
});
36+
});
37+
}
38+
}
39+
40+
/** @type {import("@rspack/core").Configuration} */
41+
module.exports = {
42+
target: "web",
43+
entry: {
44+
main: "./index.js"
45+
},
46+
externals: {
47+
path: "require('path')",
48+
fs: "require('fs')"
49+
},
50+
node: {
51+
__dirname: false
52+
},
53+
output: {
54+
crossOriginLoading: "anonymous"
55+
},
56+
plugins: [
57+
new rspack.HtmlRspackPlugin(),
58+
new rspack.experiments.SubresourceIntegrityPlugin(),
59+
new AddLinksPlugin()
60+
]
61+
};

packages/rspack/src/builtin-plugin/SubresourceIntegrityPlugin.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,16 +337,37 @@ function isErrorWithCode<T extends Error>(obj: T): boolean {
337337
);
338338
}
339339

340+
/**
341+
* Get the `src` or `href` attribute of a tag if it is a script
342+
* or link tag that needs SRI.
343+
*/
340344
function getTagSrc(tag: HtmlTagObject): string | undefined {
341-
if (!["script", "link"].includes(tag.tagName) || !tag.attributes) {
345+
if (!tag.attributes) {
342346
return undefined;
343347
}
344-
if (typeof tag.attributes.href === "string") {
345-
return tag.attributes.href;
346-
}
347-
if (typeof tag.attributes.src === "string") {
348+
349+
// Handle script tags with src attribute
350+
if (tag.tagName === "script" && typeof tag.attributes.src === "string") {
348351
return tag.attributes.src;
349352
}
353+
354+
// Handle link tags that need SRI
355+
if (tag.tagName === "link" && typeof tag.attributes.href === "string") {
356+
const rel = tag.attributes.rel;
357+
if (typeof rel !== "string") {
358+
return undefined;
359+
}
360+
361+
// Only process link tags that load actual resources
362+
const needsSRI =
363+
rel === "stylesheet" ||
364+
rel === "modulepreload" ||
365+
(rel === "preload" &&
366+
(tag.attributes.as === "script" || tag.attributes.as === "style"));
367+
368+
return needsSRI ? tag.attributes.href : undefined;
369+
}
370+
350371
return undefined;
351372
}
352373

0 commit comments

Comments
 (0)