CVE-2026-84994 Overview
CVE-2026-84994 affects Qwik, a performance-focused JavaScript framework used for server-side rendered web applications. The vulnerability resides in the default server-side qwik-json deserialization logic, where ResourceSerializer.$fill$ passes resource._resolved to Promise.resolve without rejecting attacker-controlled thenables. An unauthenticated attacker can submit a plain object whose then property is a QRL (Qwik URL) referencing an unregistered symbol. The resulting orphaned asynchronous rejection terminates Node.js under its default unhandled-rejection policy. A single request crashes the server, and repeated requests keep replicas unavailable. The issue is fixed in @builder.io/qwik 1.20.0 and @qwik.dev/core 2.0.0-beta.37.
Critical Impact
An unauthenticated remote attacker can crash Qwik server replicas with a single HTTP request, producing a sustained denial-of-service condition.
Affected Products
- @builder.io/qwik prior to 1.20.0
- @qwik.dev/core prior to 2.0.0-beta.37
- Qwik server-side rendering deployments running on Node.js with default unhandled-rejection policy
Discovery Timeline
- 2026-10-13 - CVE-2026-84994 published to NVD
- 2026-10-14 - Last updated in NVD database
Technical Details for CVE-2026-84994
Vulnerability Analysis
Qwik serializes server state into a qwik-json payload that is deserialized during resumability. The ResourceSerializer.$fill$ routine reconstructs resource objects by invoking Promise.resolve(resource._resolved). Because Promise.resolve adopts the state of any thenable passed to it, an attacker-supplied then function executes within the promise chain.
When the attacker-supplied then is a QRL pointing at an unregistered symbol, QRL resolution throws. The rejection has no attached handler, producing an unhandled promise rejection. Node.js terminates the process under its default --unhandled-rejections=throw policy, mapped to the Uncaught Exception weakness [CWE-248].
Root Cause
The deserializer trusted structural properties of the incoming JSON. It did not distinguish genuine Promise instances from attacker-crafted plain objects with a then property. Qwik also failed to attach a .catch handler to the resource promise, so any rejection became orphaned at the event loop level.
Attack Vector
The attack requires only an unauthenticated HTTP request to a Qwik-rendered endpoint that deserializes a qwik-json payload. The attacker supplies a JSON structure where a serialized resource contains a _resolved field shaped as a thenable whose then resolves to a QRL for an unregistered symbol.
};
}
+const createHandledRejectedPromise = (error: unknown) => {
+ const promise = Promise.reject(error);
+ promise.catch(() => null);
+ return promise;
+};
+
+const isThenable = (value: unknown) => {
+ if ((typeof value !== 'object' && typeof value !== 'function') || value === null) {
+ return false;
+ }
+ try {
+ return typeof (value as PromiseLike<unknown>).then === 'function';
+ } catch {
+ return true;
+ }
+};
+
const QRLSerializer = /*#__PURE__*/ serializer<QRLInternal>({
$prefix$: '\\u0002',
$test$: (v) => isQrl(v),
Source: GitHub Commit c1e7444
The patch introduces an isThenable guard and a createHandledRejectedPromise helper. The resource serializer now detects attacker-controlled thenables and attaches a .catch handler so rejections cannot escape to the global unhandled-rejection path.
Detection Methods for CVE-2026-84994
Indicators of Compromise
- Repeated Node.js process exits with messages matching UnhandledPromiseRejection originating from serializers.ts or ResourceSerializer.
- Short-lived container or pod restarts correlated with inbound POST or GET requests to Qwik SSR endpoints.
- HTTP request bodies containing qwik-json payloads with _resolved properties shaped as objects containing a then field.
Detection Strategies
- Instrument Node.js with process.on('unhandledRejection') logging to capture stack traces before termination for forensic review.
- Alert on orchestrator events, such as Kubernetes BackOff or CrashLoopBackOff, affecting Qwik SSR workloads.
- Inspect reverse-proxy access logs for unusual payload sizes or malformed JSON directed at Qwik rendering routes.
Monitoring Recommendations
- Track process uptime and restart frequency per replica; sudden drops indicate active exploitation.
- Monitor the ratio of 5xx responses and connection resets from Qwik-fronted services.
- Forward Node.js stderr containing rejection traces into a centralized log platform for correlation with request IDs.
How to Mitigate CVE-2026-84994
Immediate Actions Required
- Upgrade @builder.io/qwik to 1.20.0 or @qwik.dev/core to 2.0.0-beta.37 across all production and staging environments.
- Audit CI/CD lockfiles to confirm no transitive dependencies pin a vulnerable Qwik version.
- Rate-limit unauthenticated requests to Qwik SSR endpoints while patching is in progress.
Patch Information
The fix is tracked in GitHub Pull Request #8655 and committed as c1e7444. Full release notes are available in the GitHub Release v1.20.0 and the GitHub Security Advisory GHSA-x586-xvc8-g7q6.
Workarounds
- Run Node.js with --unhandled-rejections=warn as a temporary measure to prevent process termination, accepting the operational trade-off of silenced rejections.
- Deploy a WAF rule that blocks request bodies containing qwik-json structures with a then property inside _resolved fields.
- Place Qwik replicas behind an auto-restart supervisor combined with aggressive per-IP rate limiting until the upgrade is deployed.
# Upgrade Qwik to the patched release
npm install @builder.io/qwik@1.20.0
# or for the next-generation package
npm install @qwik.dev/core@2.0.0-beta.37
# Verify the installed version
npm ls @builder.io/qwik
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

