Title: Security: HTML attribute injection in generated tags due to missing escaping
Hi,
I found and validated a security issue in html-webpack-plugin's HTML tag serialization.
Summary
Generated HTML attributes are written without HTML escaping in lib/html-tags.js. When a generated asset URL or tag attribute contains a double quote, it breaks out of the attribute context and injects attacker-controlled attributes into the final HTML.
Validated on current main (5.6.6 in package.json).
Affected code
lib/html-tags.js
index.js
Relevant lines in the current tree:
lib/html-tags.js:46-62
index.js:417-429
index.js:569-576
Root cause
htmlTagObjectToString() serializes attributes as:
attributeName + '="' + tagDefinition.attributes[attributeName] + '"'
with no escaping for ", <, >, &, etc.
At the same time, urlencodePath() intentionally preserves the query string portion of asset URLs, so quotes placed in a filename query string survive into the generated <script> / <link> tag.
Impact
This can produce executable HTML attribute injection in emitted files.
One concrete validated case is a generated script tag:
<script defer="" src="bundle.js?x=" onerror="alert(1)"></script>
If a consumer allows untrusted input to influence asset URL query strings or tag-bearing options such as base, this becomes XSS in the generated HTML output.
Minimal reproduction
- Install dependencies:
NPM_CONFIG_CACHE=/tmp/html-webpack-plugin-npm-cache npm ci --legacy-peer-deps
- Run the attached PoC script:
node poc/validate-vulns.js
- Observe:
queryAttributeInjection.confirmed === true
queryAttributeInjection.parsedScript === "<script defer=\"\" src=\"bundle.js?x=\" onerror=\"alert(1)\"></script>"
The PoC script creates a real webpack build and then parses the emitted HTML with jsdom.
Standalone reproduction
This webpack config is sufficient:
const path = require("path");
const HtmlWebpackPlugin = require("./index.js");
module.exports = {
mode: "development",
entry: path.join(process.cwd(), "spec/fixtures/index.js"),
output: {
path: "/tmp/hwp-poc-onerror",
filename: 'bundle.js?x=" onerror="alert(1)',
},
plugins: [new HtmlWebpackPlugin({ minify: false })],
};
The emitted index.html contains:
<script defer src="bundle.js?x=" onerror="alert(1)"></script>
Expected behavior
Attribute values in generated tags should be HTML-escaped before serialization.
Suggested fix
Escape attribute values centrally in htmlTagObjectToString() before concatenation. At minimum:
& -> &
" -> "
< -> <
> -> >
It would also be worth reviewing whether innerHTML should remain raw or be clearly separated from safe attribute/tag serialization.
Secondary observations
While testing I also confirmed:
filename can emit HTML outside output.path
favicon can read and republish arbitrary local files
I am not treating those as the main report here because they are more obviously configuration trust-boundary issues. The attribute injection bug above is the cleanest and strongest issue.
If you prefer a private disclosure channel for security issues, I can move details there instead of keeping this public.
Title: Security: HTML attribute injection in generated tags due to missing escaping
Hi,
I found and validated a security issue in
html-webpack-plugin's HTML tag serialization.Summary
Generated HTML attributes are written without HTML escaping in
lib/html-tags.js. When a generated asset URL or tag attribute contains a double quote, it breaks out of the attribute context and injects attacker-controlled attributes into the final HTML.Validated on current
main(5.6.6inpackage.json).Affected code
lib/html-tags.jsindex.jsRelevant lines in the current tree:
lib/html-tags.js:46-62index.js:417-429index.js:569-576Root cause
htmlTagObjectToString()serializes attributes as:with no escaping for
",<,>,&, etc.At the same time,
urlencodePath()intentionally preserves the query string portion of asset URLs, so quotes placed in a filename query string survive into the generated<script>/<link>tag.Impact
This can produce executable HTML attribute injection in emitted files.
One concrete validated case is a generated script tag:
If a consumer allows untrusted input to influence asset URL query strings or tag-bearing options such as
base, this becomes XSS in the generated HTML output.Minimal reproduction
queryAttributeInjection.confirmed === truequeryAttributeInjection.parsedScript === "<script defer=\"\" src=\"bundle.js?x=\" onerror=\"alert(1)\"></script>"The PoC script creates a real webpack build and then parses the emitted HTML with
jsdom.Standalone reproduction
This webpack config is sufficient:
The emitted
index.htmlcontains:Expected behavior
Attribute values in generated tags should be HTML-escaped before serialization.
Suggested fix
Escape attribute values centrally in
htmlTagObjectToString()before concatenation. At minimum:&->&"->"<-><>->>It would also be worth reviewing whether
innerHTMLshould remain raw or be clearly separated from safe attribute/tag serialization.Secondary observations
While testing I also confirmed:
filenamecan emit HTML outsideoutput.pathfaviconcan read and republish arbitrary local filesI am not treating those as the main report here because they are more obviously configuration trust-boundary issues. The attribute injection bug above is the cleanest and strongest issue.
If you prefer a private disclosure channel for security issues, I can move details there instead of keeping this public.