GitHub Advisoryadvisory · npm
GHSA-x7mm-9vvv-64w8 unhead: Streaming SSR `streamKey` injected into inline script without identifier validation
LOW
Official page ↗
- Ecosystem
- npm
- Packages
- unhead
- Severity
- Low
- CVSS vector
- CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N
- Weaknesses
- CWE-79
- Reviewed
- 2026-04-10
Affected packages
• npm unhead: >= 3.0.0-beta.5, < 3.0.1 → fixed in 3.0.1
Description
Summary
`createStreamableHead({ streamKey })` interpolated its `streamKey` argument directly into the streaming SSR bootstrap and suspense-chunk inline scripts without identifier validation or escaping. If an application forwards untrusted data into that configuration value, the rendered scripts become a script-injection sink.
Details
`streamKey` was embedded into JavaScript source via dot notation in two public helpers:
• `createBootstrapScript()` returned `window.${streamKey}={...}`
• `renderSSRHeadSuspenseChunk()` returned `window.${streamKey}.push(...)`
No escaping, quoting, or identifier validation was applied before these strings were embedded into HTML. A `streamKey` such as `__unhead__;globalThis.PWNED=1;//` broke out of the intended property access and injected arbitrary JavaScript into the page. The JSON escaping used for streamed head entries did not protect `streamKey` because `streamKey` was inserted as raw code rather than as serialized data.
Impact
`streamKey` is a developer-chosen configuration value rather than a data field — the intended usage is a hardcoded identifier-shaped constant (default `__unhead__`). Exploitation therefore requires an application to explicitly route untrusted input into a configuration sink, which is not a documented or recommended pattern. We have no reports of any downstream project sourcing `streamKey` from request data.
Applications using the default `streamKey`, or any hardcoded custom key, are not affected.
PoC
```ts
import { createStreamableHead, renderSSRHeadShell } from 'unhead/stream/server'
const { head } = createStreamableHead({
streamKey: '__unhead__;globalThis.PWNED=1;//',
})
const html = renderSSRHeadShell(
head,
'',
)
// window.__unhead__;globalThis.PWNED=1;//={_q:[],push(e){this._q.push(e)}}…
```
Patch
Fixed on `main` in `64b5ac0`. The fix will ship in the next patch release of `unhead`.
`streamKey` is now validated against a conservative ASCII JavaScript-identifier pattern (`/^[$_a-z][$\w]*$/i`) at every sink — `createStreamableHead`, `createBootstrapScript`, and the internal stream-key resolver. Invalid values throw immediately instead of being emitted into script output.
Workarounds
Do not pass untrusted data into `createStreamableHead({ streamKey })` or `createBootstrapScript(key)`. If per-tenant keys are required, whitelist them against an identifier-safe pattern before constructing the head instance.
Credit
Thanks to @Jvr2022 for the report.
Related
Weakness · CWE (1)
- CWE-79 Improper Neutralization of Input During Web Page Generation weakness