jantimon / jantimon/html-webpack-plugin

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

Open
#1,885 3 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
10.7k
Forks
1.3k
Avg merge
1m
Merged PRs (30d)
1

Description

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:

```js
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 `` / `<link>` tag.

Impact

This can produce executable HTML attribute injection in emitted files.

One concrete validated case is a generated script tag:

```html
<script defer="" src="bundle.js?x=" onerror="alert(1)">
```

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:

```bash
NPM_CONFIG_CACHE=/tmp/html-webpack-plugin-npm-cache npm ci --legacy-peer-deps
```

2. Run the attached PoC script:

```bash
node poc/validate-vulns.js
```

3. Observe:

- `queryAttributeInjection.confirmed === true`
- `queryAttributeInjection.parsedScript === ""`

The PoC script creates a real webpack build and then parses the emitted HTML with `jsdom`.

Standalone reproduction

This webpack config is sufficient:

```js
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:

```html

```

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.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reading htmlTagObjectToString() in lib/html-tags.js, then run node poc/validate-vulns.js to reproduce the issue through the generated HTML. Review the relevant call sites in index.js and verify that attribute values are escaped in emitted script and link tags while the PoC no longer confirms injection.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, webpack
Domain
frontend, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.