CVE-2026-2391
Low · 3.7CVSS
3.7
EPSS
0.5%
Published
Feb 12, 2026
CISA due date
—
Cloudanix analysis
A specially crafted web request can make some Node.js applications run out of memory and become unavailable.
- Exploitation
- Remote Unauthenticated
- Attacker needs
- None
- Complexity
- Low
- Impact
- Denial Of Service
- Component
- Library
- Class
- Denial of service
- Remediation
- Upgrade
- Effort
- Medium
- Patch risk
- Low
What it actually does
The flaw is a denial-of-service condition in the Node.js `qs` query-string parser when applications enable the non-default `comma: true` option. In that mode, comma-separated parameter values are split into arrays before `arrayLimit` and `throwOnLimitExceeded` checks are applied. As a result, a single query parameter containing a very large number of commas can cause `qs.parse()` to allocate an extremely large array, consuming process memory and potentially crashing the Node.js runtime or the hosting service.
Why it matters to the business
Successful exploitation can make affected web applications or APIs slow, unstable, or unavailable by exhausting server memory. The risk is most relevant for internet-facing Node.js services that parse attacker-controlled query strings with `qs` and explicitly enable comma parsing. The CVSS and EPSS values indicate relatively low current exploitation likelihood, it is not in CISA KEV, and no ransomware use is reported, but the issue has been public for 178 days and can still affect availability if exposed endpoints accept large query strings.
How an attack unfolds
An attacker identifies an endpoint backed by a Node.js application or framework path that uses `qs.parse()` with `comma: true`. The attacker sends HTTP requests containing one or more query parameters with very long comma-separated values, such as a parameter whose value is mostly repeated commas. Because the vulnerable parser splits the value into an array before enforcing `arrayLimit`, the application allocates memory proportional to the number of comma-separated elements instead of rejecting the input. Repeated or oversized requests can drive high memory use, garbage-collection pressure, process crashes, container restarts, or service-level outages. No authentication or user interaction is required if the vulnerable parsing path is reachable over the network.
How to fix it
Upgrade `ljharb/qs` to a version that includes commit `f6a7abff1f13d644db9b05fe4f2c98ada6bf8482`, or pin directly to that fixed commit where consuming from Git. Deduplicate and update all transitive copies through package-lock, yarn.lock, pnpm-lock.yaml, or equivalent lockfiles. If immediate upgrade is not possible, disable `comma: true` where not strictly required, enforce maximum request-line and query-string sizes at the reverse proxy or application layer, reject parameters with excessive comma counts, set conservative body/query parser limits, and ensure process supervisors impose memory limits with graceful restart behavior. Treat mitigation as defense in depth; the preferred fix is to consume a patched `qs` release or the fixed commit.
How to confirm the fix worked
Confirm exposure by inventorying applications and lockfiles for the `qs` package and verifying whether any code path calls `qs.parse()` or a framework wrapper with `comma: true`. Confirm remediation by checking that the resolved dependency contains commit `f6a7abff1f13d644db9b05fe4f2c98ada6bf8482` or a later release that includes it. In a non-production environment, parse a parameter with more comma-separated elements than the configured `arrayLimit` while `throwOnLimitExceeded` is enabled and verify that the parser enforces the limit rather than returning an oversized array. Also validate that edge controls reject oversized query strings and that SCA/dependency scanning no longer reports the vulnerable `qs` version.
Typical attack path
- Find a web application or API endpoint that accepts query-string parameters.
- Determine or assume that the backend uses the `qs` package with `comma: true` enabled.
- Send a request containing a query parameter with an unusually large number of comma-separated empty or short values.
- Trigger `qs.parse()` to call `split(',')` and allocate an oversized array before `arrayLimit` is enforced.
- Repeat or enlarge requests until Node.js memory usage spikes, garbage collection stalls, or the process crashes.
- Cause user-visible errors, restarts, degraded performance, or temporary service unavailability.
How to detect it
To determine whether exploitation already occurred, review HTTP access logs, CDN/WAF logs, reverse proxy logs, and application request logs for unusually long query strings containing high counts of commas in a single parameter value. Correlate those requests with spikes in Node.js heap usage, garbage-collection pauses, HTTP 500/502/503/504 responses, request timeouts, container OOM kills, systemd or PM2 restarts, Kubernetes pod restarts, and V8 fatal memory errors. In application logs, look for crashes or restarts immediately after requests with comma-dense query parameters. In infrastructure telemetry, review memory metrics and restart events for Node.js services around the same timestamps. If available, preserve suspicious request samples, source IPs, user agents, and heap or crash dumps for scoping, but avoid replaying large payloads against production systems.
- FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
- FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
- JavaScript heap out of memory
- Allocation failed - JavaScript heap out of memory
- signal: killed
- OOMKilled
- ExitCode:137
- exit code 137
- ENOMEM
- qs.parse
- comma: true
- ?,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
- =,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
- HTTP 414
- Request-URI Too Large
- Request Header Or Cookie Too Large
What your security team should do
- Prioritize internet-facing Node.js services and APIs that parse query strings with `qs` and may enable `comma: true`.
- Upgrade or pin `qs` to a build containing commit `f6a7abff1f13d644db9b05fe4f2c98ada6bf8482`, then rebuild and redeploy affected services.
- Regenerate lockfiles and container images to eliminate stale or transitive vulnerable copies of `qs`.
- Add reverse proxy, load balancer, or application rules limiting maximum URL/query-string length and rejecting query parameters with excessive comma density.
- Review application code for unnecessary `comma: true` usage and disable it unless required by a documented API contract.
- Monitor for Node.js memory exhaustion, process restarts, and anomalously long comma-heavy query strings, especially on public endpoints.
- Because this is not KEV-listed and has low EPSS, handle as availability-hardening work unless exposure is confirmed on critical public services, in which case patch promptly.
The record
### Summary The `arrayLimit` option in qs does not enforce limits for comma-separated values when `comma: true` is enabled, allowing attackers to cause denial-of-service via memory exhaustion. This is a bypass of the array limit enforcement, similar to the bracket notation bypass addressed in GHSA-6rw7-vpxm-498p (CVE-2025-15284). ### Details When the `comma` option is set to `true` (not the default, but configurable in applications), qs allows parsing comma-separated strings as arrays (e.g., `?param=a,b,c` becomes `['a', 'b', 'c']`). However, the limit check for `arrayLimit` (default: 20) and the optional throwOnLimitExceeded occur after the comma-handling logic in `parseArrayValue`, enabling a bypass. This permits creation of arbitrarily large arrays from a single parameter, leading to excessive memory allocation. **Vulnerable code** (lib/parse.js: lines ~40-50): ```js if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { return val.split(','); } if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) { throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.'); } return val; ``` The `split(',')` returns the array immediately, skipping the subsequent limit check. Downstream merging via `utils.combine` does not prevent allocation, even if it marks overflows for sparse arrays.This discrepancy allows attackers to send a single parameter with millions of commas (e.g., `?param=,,,,,,,,...`), allocating massive arrays in memory without triggering limits. It bypasses the intent of `arrayLimit`, which is enforced correctly for indexed (`a[0]=`) and bracket (`a[]=`) notations (the latter fixed in v6.14.1 per GHSA-6rw7-vpxm-498p). ### PoC **Test 1 - Basic bypass:** ``` npm install qs ``` ```js const qs = require('qs'); const payload = 'a=' + ','.repeat(25); // 26 elements after split (bypasses arrayLimit: 5) const options = { comma: true, arrayLimit: 5, throwOnLimitExceeded: true }; try { const result = qs.parse(payload, options); console.log(result.a.length); // Outputs: 26 (bypass successful) } catch (e) { console.log('Limit enforced:', e.message); // Not thrown } ``` **Configuration:** - `comma: true` - `arrayLimit: 5` - `throwOnLimitExceeded: true` Expected: Throws "Array limit exceeded" error. Actual: Parses successfully, creating an array of length 26. ### Impact Denial of Service (DoS) via memory exhaustion.
Affected packages
| Ecosystem | Package | Introduced | Fixed |
|---|---|---|---|
| Chainguard | arangodb-3.11 | 0 | 3.11.14.3-r0 |
| Chainguard | arangodb-3.12 | 0 | 3.12.7.2-r2 |
| Chainguard | argo-workflows-ui-3.6 | 0 | 3.6.19-r0 |
| Chainguard | argo-workflows-ui-3.7 | 0 | 3.7.10-r0 |
| Chainguard | code-server | 0 | 4.106.3-r5 |
| Chainguard | json-server | 0 | 0.17.4-r5 |
| Chainguard | kubeflow-centraldashboard | 0 | 1.10.0-r11 |
| Chainguard | kubeflow-pipelines-frontend | 0 | 2.15.0-r12 |
| Chainguard | langfuse-3 | 0 | 3.155.1-r2 |
| Chainguard | langfuse-3-worker | 0 | 3.155.1-r2 |
| Chainguard | langfuse-fips-3 | 0 | 3.155.1-r1 |
| Chainguard | langfuse-fips-3-worker | 0 | 3.155.1-r1 |
| Chainguard | librechat | 0 | 0.8.3-r1 |
| Chainguard | opensearch-dashboards-2 | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-alerting-dashboards-plugin | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-dashboards-notifications | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-dashboards-query-workbench | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-dashboards-reporting | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-dashboards-visualizations | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-fips | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-fips-alerting-dashboards-plugin | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-fips-dashboards-notifications | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-fips-dashboards-query-workbench | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-fips-dashboards-reporting | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-fips-dashboards-visualizations | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-fips-index-management-dashboards-plugin | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-fips-security-analytics-dashboards-plugin | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-fips-security-dashboards-plugin | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-index-management-dashboards-plugin | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-security-analytics-dashboards-plugin | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-2-security-dashboards-plugin | 0 | 2.19.4-r10 |
| Chainguard | opensearch-dashboards-3 | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-alerting-dashboards-plugin | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-dashboards-notifications | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-dashboards-query-workbench | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-dashboards-reporting | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-fips | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-fips-alerting-dashboards-plugin | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-fips-dashboards-notifications | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-fips-dashboards-query-workbench | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-fips-dashboards-reporting | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-fips-index-management-dashboards-plugin | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-fips-security-analytics-dashboards-plugin | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-fips-security-dashboards-plugin | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-index-management-dashboards-plugin | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-security-analytics-dashboards-plugin | 0 | 3.5.0-r1 |
| Chainguard | opensearch-dashboards-3-security-dashboards-plugin | 0 | 3.5.0-r1 |
| Chainguard | pelias-api | 0 | 7.6.0-r2 |
| Chainguard | redisinsight | 0 | 3.0.3-r1 |
| Chainguard | sqlpad | 0 | 7.5.7-r10 |
| Chainguard | thingsboard-tb-js-executor | 0 | 4.3.0.1-r1 |
| Chainguard | thingsboard-tb-js-executor-fips | 0 | 4.3.0.1-r1 |
| Chainguard | thingsboard-tb-web-ui | 0 | 4.3.0.1-r1 |
| Chainguard | thingsboard-tb-web-ui-fips | 0 | 4.3.0.1-r1 |
| Chainguard | tileserver-gl | 0 | 5.5.0-r4 |
| Chainguard | tileserver-gl-fips | 0 | 5.5.0-r5 |
| Wolfi | argo-workflows-ui-3.7 | 0 | 3.7.10-r0 |
| Wolfi | code-server | 0 | 4.106.3-r5 |
| Wolfi | json-server | 0 | 0.17.4-r5 |
| Wolfi | kubeflow-centraldashboard | 0 | 1.10.0-r11 |
| Wolfi | kubeflow-pipelines-frontend | 0 | 2.15.0-r12 |
| Wolfi | langfuse-3 | 0 | 3.155.1-r2 |
| Wolfi | langfuse-3-worker | 0 | 3.155.1-r2 |
| Wolfi | opensearch-dashboards-2 | 0 | 2.19.4-r10 |
| Wolfi | opensearch-dashboards-2-alerting-dashboards-plugin | 0 | 2.19.4-r10 |
| Wolfi | opensearch-dashboards-2-dashboards-notifications | 0 | 2.19.4-r10 |
| Wolfi | opensearch-dashboards-2-dashboards-query-workbench | 0 | 2.19.4-r10 |
| Wolfi | opensearch-dashboards-2-dashboards-reporting | 0 | 2.19.4-r10 |
| Wolfi | opensearch-dashboards-2-dashboards-visualizations | 0 | 2.19.4-r10 |
| Wolfi | opensearch-dashboards-2-index-management-dashboards-plugin | 0 | 2.19.4-r10 |
| Wolfi | opensearch-dashboards-2-security-analytics-dashboards-plugin | 0 | 2.19.4-r10 |
| Wolfi | opensearch-dashboards-2-security-dashboards-plugin | 0 | 2.19.4-r10 |
| Wolfi | opensearch-dashboards-3 | 0 | 3.5.0-r1 |
| Wolfi | opensearch-dashboards-3-alerting-dashboards-plugin | 0 | 3.5.0-r1 |
| Wolfi | opensearch-dashboards-3-dashboards-notifications | 0 | 3.5.0-r1 |
| Wolfi | opensearch-dashboards-3-dashboards-query-workbench | 0 | 3.5.0-r1 |
| Wolfi | opensearch-dashboards-3-dashboards-reporting | 0 | 3.5.0-r1 |
| Wolfi | opensearch-dashboards-3-index-management-dashboards-plugin | 0 | 3.5.0-r1 |
| Wolfi | opensearch-dashboards-3-security-analytics-dashboards-plugin | 0 | 3.5.0-r1 |
| Wolfi | opensearch-dashboards-3-security-dashboards-plugin | 0 | 3.5.0-r1 |
| Wolfi | sqlpad | 0 | 7.5.7-r10 |
| Wolfi | thingsboard-tb-js-executor | 0 | 4.3.0.1-r1 |
| Wolfi | thingsboard-tb-web-ui | 0 | 4.3.0.1-r1 |
| Wolfi | tileserver-gl | 0 | 5.5.0-r4 |
Related vulnerabilities
- GHSA-w7fw-mjwx-w883 qs's arrayLimit bypass in comma parsing allows denial of service Same vulnerability
- GHSA-r5fr-rjxr-66jc lodash vulnerable to Code Injection via `_.template` imports key names Also affects arangodb-3.11
- GHSA-f23m-r3pf-42rh lodash vulnerable to Prototype Pollution via array path bypass in `_.unset` and `_.omit` Also affects arangodb-3.11
- CVE-2026-4800 Impact: The fix for CVE-2021-23337 (https://github.com/advisories/GHSA-35jh-r3h4-6jhm)… Also affects arangodb-3.11
- CVE-2026-2950 Impact: Lodash versions 4.17.23 and earlier are vulnerable to prototype pollution in the… Also affects arangodb-3.11
- CVE-2026-33228 flatted is a circular JSON parser. Also affects arangodb-3.11
- GHSA-rf6f-7fwh-wjgh Prototype Pollution via parse() in NodeJS flatted Also affects arangodb-3.11
- GHSA-25h7-pfq9-p65f flatted vulnerable to unbounded recursion DoS in parse() revive phase Also affects arangodb-3.11