Back to Blog
high SEVERITY6 min read

How Server-Sent Events Injection via Unsanitized Newlines happens in Node.js h3 and how to fix it

A high-severity Server-Sent Events (SSE) injection vulnerability (CVE-2026-33128) was discovered in the h3 HTTP framework, where unsanitized newline characters in event stream fields could allow attackers to inject arbitrary SSE messages. The fix upgrades h3 from version 1.15.5 to 1.15.6 in the frontend's dependency tree, ensuring that newline characters are properly sanitized before being written to event streams.

O
By Orbis AppSec
Published July 12, 2026Reviewed July 12, 2026

Answer Summary

CVE-2026-33128 is a Server-Sent Events (SSE) injection vulnerability in the h3 Node.js HTTP framework (CWE-93: CRLF Injection) where unsanitized newline characters in event stream fields allow attackers to inject arbitrary events into SSE connections. The fix is to upgrade h3 from version 1.15.5 to 1.15.6 (or 2.0.1-rc.15 for the v2 branch), which adds proper newline sanitization to all SSE field outputs.

Vulnerability at a Glance

cweCWE-93 (Improper Neutralization of CRLF Sequences)
fixUpgrade h3 from 1.15.5 to 1.15.6 which adds newline sanitization in SSE output
riskAttackers can inject arbitrary SSE events to manipulate client-side behavior
languageJavaScript/Node.js (h3 framework)
root causeh3 did not sanitize newline characters (\n, \r) in SSE event stream field values
vulnerabilityServer-Sent Events Injection via Unsanitized Newlines

How Server-Sent Events Injection via Unsanitized Newlines happens in Node.js h3 and how to fix it

Introduction

In a frontend application's dependency tree, we discovered a high-severity SSE injection vulnerability tracked as CVE-2026-33128 in the h3 HTTP framework. The vulnerable version — h3 1.15.5 — was pinned as a transitive dependency in frontend/pnpm-lock.yaml, meaning any Server-Sent Events endpoint served by the application's Nitro/Nuxt-based backend was susceptible to event stream manipulation.

The h3 framework is a minimal, high-performance HTTP framework for Node.js that powers Nitro, Nuxt, and many other tools in the UnJS ecosystem. Its SSE implementation failed to sanitize newline characters (\n, \r) in event stream field values, allowing an attacker who could influence the content of SSE fields to inject entirely new, fabricated events into the stream.

This matters because SSE is increasingly used for real-time features like notifications, live updates, and AI streaming responses — all contexts where injected events could trigger dangerous client-side behavior.

The Vulnerability Explained

How Server-Sent Events Work

The SSE protocol (text/event-stream) uses a simple text-based format where fields are separated by newlines:

event: message
data: Hello, world!

event: update
data: {"status": "complete"}

Each event is terminated by a double newline (\n\n). Individual fields (event:, data:, id:, retry:) are terminated by a single newline. This means newline characters have protocol-level significance.

The Flaw in h3 1.15.5

In h3 version 1.15.5, when constructing SSE event fields, the framework did not strip or encode newline characters from values passed into event stream construction. If application code passed user-influenced data into an SSE field — for example, setting the data field from a database record or API response that an attacker could influence — the attacker could embed newlines to break out of the current field and inject entirely new SSE events.

Consider a scenario where an application streams status updates:

import { createEventStream } from 'h3';

// Attacker controls statusMessage via earlier input
const statusMessage = userInput; // e.g., "done\n\nevent: admin\ndata: {\"role\":\"admin\"}"

eventStream.push({
  event: 'status',
  data: statusMessage
});

With h3 1.15.5, this would produce:

event: status
data: done

event: admin
data: {"role":"admin"}

The client's EventSource would interpret this as two separate events — the legitimate status event and a fabricated admin event that the attacker injected.

Attack Scenario for This Application

Given that this is a frontend application (likely Nuxt/Nitro-based given the h3 dependency), any SSE endpoint that includes user-influenced content in event fields is vulnerable. For example:

  1. An attacker submits content containing crafted newline sequences through normal application input (form fields, API calls, file uploads)
  2. The application stores this content and later streams it via SSE to connected clients
  3. The injected SSE events are delivered to all clients listening on that stream
  4. Client-side JavaScript handlers process the fabricated events as legitimate, potentially triggering unauthorized actions, displaying false information, or executing stored XSS payloads

The threat model notes this is a local CLI tool where "exploitation requires the attacker to control command-line arguments or input files." Even in this context, a malicious input file processed by the tool could inject events into any SSE stream the tool exposes during operation (e.g., a development server's hot-reload stream).

The Fix

The fix involves two changes across frontend/package.json and frontend/pnpm-lock.yaml:

1. Pinning h3 1.15.6 as a direct dependency (frontend/package.json)

// Before: h3 was only a transitive dependency (1.15.5)
// After: h3 is pinned as a direct dependency at the fixed version
{
  "dependencies": {
    "dotenv": "^17.2.3",
    "embla-carousel-react": "^8.6.0",
    "gsap": "^3.13.0",
+   "h3": "1.15.6",
    "hast": "^1.0.0",
    "katex": "^0.16.28",
    "lucide-react": "^0.562.0"
  }
}

By adding h3 as a direct dependency with an exact version pin (1.15.6, not ^1.15.6), the fix ensures that pnpm resolves to the patched version regardless of what other transitive dependencies might request.

2. Lock file resolution (frontend/pnpm-lock.yaml)

# Before
-  h3@1.15.5:
-    resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==}

# After
+  h3@1.15.6:
+    resolution: {integrity: sha512-oi15ESLW5LRthZ+qPCi5GNasY/gvynSKUQxgiovrY63bPAtG59wtM+LSrlcwvOHAXzGrXVLnI97brbkdPF9WoQ==}

The lock file now resolves to h3 1.15.6, which includes proper newline sanitization in all SSE field construction. The fix also removes the now-unnecessary cookie-es@1.2.2 resolution (replaced by 1.2.3) and updates ufo from 1.6.3 to 1.6.4 as part of the dependency tree reconciliation.

Why h3 1.15.6 Fixes the Issue

In h3 1.15.6, the SSE implementation sanitizes newline characters (\n, \r, \r\n) from all field values before writing them to the event stream. This means even if user-controlled data containing newlines reaches the SSE construction logic, the newlines are stripped or encoded, preventing protocol-level injection.

Prevention & Best Practices

  1. Always sanitize SSE field values: Even if your framework handles it, defense-in-depth means stripping \n and \r from any user-influenced data before passing it to SSE construction.

  2. Pin security-critical dependencies: Use exact version pins for security-critical packages rather than range specifiers. This prevents regression if a vulnerable version is re-introduced through dependency resolution.

  3. Enable dependency scanning in CI: Tools like Trivy, Snyk, or GitHub Dependabot can catch known vulnerabilities in your dependency tree before they reach production.

  4. Treat SSE like any injection context: Just as you sanitize HTML output to prevent XSS and SQL parameters to prevent SQLi, sanitize SSE field values to prevent event injection.

  5. Monitor the UnJS security advisories: The h3/Nitro/Nuxt ecosystem is widely used — subscribe to security notifications for these packages.

Key Takeaways

  • h3 1.15.5's SSE implementation treated newlines in field values as literal protocol delimiters, enabling injection of arbitrary events — upgrading to 1.15.6 adds the missing sanitization.
  • Transitive dependencies are attack surface: h3 wasn't a direct dependency in this project, but its vulnerability was still exploitable through the framework stack.
  • Pinning the fixed version as a direct dependency ("h3": "1.15.6" in package.json) is the most reliable way to override a vulnerable transitive dependency in pnpm.
  • SSE injection is the event-stream equivalent of HTTP header injection (CRLF injection) — both exploit the same class of newline-as-delimiter semantics.
  • Even local CLI tools with dev servers can be affected when they expose SSE endpoints (e.g., HMR/hot-reload streams).

How Orbis AppSec Detected This

  • Source: User-influenced data flowing into h3's SSE event stream construction (any createEventStream or eventStream.push() call with unsanitized field values)
  • Sink: h3's internal SSE serialization in versions ≤ 1.15.5, which writes field values directly to the text/event-stream response without newline sanitization
  • Missing control: No sanitization or encoding of \n and \r characters in SSE field values before protocol serialization
  • CWE: CWE-93 (Improper Neutralization of CRLF Sequences in HTTP Headers)
  • Fix: Upgraded h3 from 1.15.5 to 1.15.6, which adds newline sanitization to all SSE field outputs

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

CVE-2026-33128 is a reminder that protocol-level injection vulnerabilities lurk in unexpected places. Server-Sent Events may seem like a simple, read-only streaming protocol, but the newline-delimited format creates an injection surface identical in principle to CRLF injection in HTTP headers. By upgrading h3 to 1.15.6, this project eliminated the risk of SSE event fabrication, protecting clients from receiving and acting on attacker-crafted events.

For any project using h3, Nitro, or Nuxt with SSE endpoints: check your h3 version and upgrade immediately. The fix is a drop-in replacement with no breaking changes.

References

Frequently Asked Questions

What is Server-Sent Events Injection?

SSE Injection occurs when an attacker can insert newline characters into event stream fields, breaking the SSE protocol framing and injecting arbitrary events that the client interprets as legitimate server messages.

How do you prevent SSE Injection in Node.js?

Sanitize all user-controlled data before including it in SSE fields by stripping or encoding newline characters (\n, \r, \r\n), and use frameworks that handle this sanitization automatically like h3 >= 1.15.6.

What CWE is SSE Injection?

SSE Injection falls under CWE-93 (Improper Neutralization of CRLF Sequences in HTTP Headers), as it exploits the same class of newline injection but in the context of the text/event-stream protocol.

Is output encoding enough to prevent SSE Injection?

Standard HTML output encoding is not sufficient — SSE requires specific newline sanitization because the protocol uses newlines as field delimiters. You must strip or replace \n and \r characters in all field values.

Can static analysis detect SSE Injection?

Yes, tools like Trivy can detect known vulnerable versions of libraries like h3 through dependency scanning, and SAST tools can flag patterns where user input flows into SSE event construction without newline sanitization.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #4125

Related Articles

high

How missing Dependabot cooldown happens in GitHub Actions and how to fix it

A high-severity configuration vulnerability was discovered in a `.github/dependabot.yml` file that lacked a cooldown period for package updates. Without this safeguard, Dependabot could immediately propose updates to newly published package versions—including potentially malicious or unstable releases. The fix adds a simple `cooldown` block with a 7-day waiting period before any new package version is suggested.

high

How Memory Exhaustion via Large Comma-Separated Selector Lists happens in Python Soup Sieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in Soup Sieve version 2.8.3, affecting Python applications that parse CSS selectors from user-controlled input. The vulnerability allows attackers to craft malicious selector lists that consume excessive memory, potentially causing denial of service. The fix involves upgrading to soupsieve 2.8.4, which implements proper resource limits on selector parsing.

high

How prototype pollution via `__proto__` key happens in Node.js defu and how to fix it

A high-severity prototype pollution vulnerability (CVE-2026-35209) was discovered in the `defu` package version 6.1.4, which allowed attackers to inject properties into JavaScript's `Object.prototype` via the `__proto__` key in defaults arguments. The fix upgrades `defu` to version 6.1.5 in the frontend's dependency tree, protecting downstream consumers like `c12` and `dotenv` configuration loaders from malicious property injection.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.

high

How memory exhaustion via large comma-separated selector lists happens in Python soupsieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in soupsieve 2.8.3, a CSS selector library used by BeautifulSoup in Python. An attacker who could influence CSS selector input could craft large comma-separated selector lists to exhaust system memory, causing denial of service. The fix upgrades soupsieve from 2.8.3 to 2.8.4 in the backend's `uv.lock` dependency file.

high

How buffer overflow from unsafe string copy functions happens in C network interface code and how to fix it

A high-severity buffer overflow vulnerability was discovered in `generic/eth-impl.c`, where unsafe `strncpy()` and `sprintf()` calls could write beyond buffer boundaries when handling network interface names and device filenames. The fix replaced these dangerous functions with bounded `snprintf()` calls that guarantee null-termination and prevent memory corruption.