Skip to content

Security: HTML attribute injection in generated tags due to missing escaping #1885

@x4cc3

Description

@x4cc3

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

  1. Install dependencies:
NPM_CONFIG_CACHE=/tmp/html-webpack-plugin-npm-cache npm ci --legacy-peer-deps
  1. Run the attached PoC script:
node poc/validate-vulns.js
  1. 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:

  • & -> &amp;
  • " -> &quot;
  • < -> &lt;
  • > -> &gt;

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions