Axios DoS via Unbounded Stream Consumption: How responseType: 'stream' Became an Attack Vector
Introduction
The pnpm-lock.yaml file in this project pins axios at version 1.12.2 — a version that contains a quietly dangerous flaw. When HTTP responses are consumed using responseType: 'stream', axios fails to enforce any upper bound on how much data it will accept from the remote server. The result? A malicious or misconfigured server can push an endless stream of bytes into your Node.js process, consuming memory until the application crashes or becomes unresponsive.
This is CVE-2026-42036, a medium-severity Denial of Service vulnerability that affects any application using axios with streaming responses. If your backend proxies external content, fetches large files, or pipes API responses — this vulnerability deserves your full attention.
The Vulnerability Explained
What Goes Wrong with responseType: 'stream'?
When you configure an axios request like this:
const response = await axios.get('https://external-api.example.com/data', {
responseType: 'stream'
});
response.data.pipe(someWritableStream);
You're telling axios: "Don't buffer this response — give me the raw Node.js readable stream." This is a perfectly legitimate pattern for handling large files, real-time data feeds, or proxying responses.
The problem in axios 1.12.2 is that the stream handed back to the caller has no built-in consumption limit. Axios does not apply a maxContentLength guard when the response type is stream, nor does it enforce a timeout on how long an idle or slow stream can hold a connection open.
The Attack Scenario
Consider a Node.js service that proxies responses from a third-party API:
// A typical proxy handler — looks safe, but isn't with axios 1.12.2
app.get('/proxy', async (req, res) => {
const upstream = await axios.get(req.query.url, {
responseType: 'stream'
});
upstream.data.pipe(res);
});
An attacker — or a compromised upstream server — can respond to this request with an HTTP response that never ends, or that sends data at a trickle for hours. Because axios 1.12.2 does not cap stream consumption:
- The Node.js event loop remains tied to this connection.
- Memory allocated for buffering grows without bound.
- With enough concurrent requests of this type, the process exhausts available heap memory.
- The application crashes or becomes too slow to serve legitimate users.
This is a classic slow-read / infinite-stream DoS attack. The attacker doesn't need to send data fast — they just need to keep the connection alive and growing.
Why maxContentLength Wasn't Enough
Axios does expose a maxContentLength option, but in 1.12.2, this guard was not reliably enforced for streaming responses. The check was designed for buffered responses where axios accumulates the full body before resolving the promise. When responseType: 'stream' bypasses that accumulation step, the guard is effectively skipped — leaving the stream unbounded.
The Fix
What Changed: axios 1.12.2 → 1.15.1
The remediation is a direct dependency upgrade captured in pnpm-lock.yaml:
# Before (vulnerable)
axios:
version: 1.12.2
# After (fixed)
axios:
version: 1.15.1
Axios 1.15.1 addresses the unbounded stream consumption issue by ensuring that stream responses are subject to the same content-length and timeout enforcement as buffered responses. Specifically, the fix in the axios codebase:
- Enforces
maxContentLengthfor stream responses — the readable stream now tracks bytes transferred and destroys the stream if the configured limit is exceeded. - Applies response timeout to streaming connections — a connection that stalls mid-stream will now be terminated after the configured timeout window, rather than being held open indefinitely.
- Emits a proper error event on the stream when limits are exceeded, allowing callers to handle the rejection gracefully rather than experiencing a silent memory leak.
Before vs. After Behavior
Before (axios 1.12.2):
// maxContentLength is set, but ignored for streams
const response = await axios.get(url, {
responseType: 'stream',
maxContentLength: 10 * 1024 * 1024 // 10MB — NOT enforced in 1.12.2
});
// Stream can grow past 10MB with no error thrown
response.data.pipe(destination);
After (axios 1.15.1):
// maxContentLength is now enforced even for streams
const response = await axios.get(url, {
responseType: 'stream',
maxContentLength: 10 * 1024 * 1024 // 10MB — ENFORCED in 1.15.1
});
// Stream is destroyed and an error is emitted if 10MB is exceeded
response.data.on('error', (err) => {
console.error('Stream limit exceeded:', err.message);
});
response.data.pipe(destination);
This is a non-breaking change for well-behaved applications — if you weren't hitting content limits before, you won't notice a difference. But it closes the door on the unbounded consumption attack.
Prevention & Best Practices
1. Always Configure maxContentLength and timeout for Stream Requests
Even with the fix in place, defense-in-depth means you should explicitly configure limits:
const response = await axios.get(url, {
responseType: 'stream',
maxContentLength: 50 * 1024 * 1024, // 50MB hard limit
timeout: 30000, // 30 second connection timeout
maxBodyLength: 50 * 1024 * 1024
});
2. Never Proxy Arbitrary User-Supplied URLs Without Validation
The attack scenario above becomes dramatically more dangerous when users control the upstream URL. Validate and allowlist upstream hosts before making proxied requests:
const ALLOWED_HOSTS = new Set(['api.trusted.com', 'cdn.trusted.com']);
function isSafeUrl(rawUrl) {
try {
const parsed = new URL(rawUrl);
return ALLOWED_HOSTS.has(parsed.hostname);
} catch {
return false;
}
}
3. Audit pnpm-lock.yaml (and package-lock.json) Regularly
Lock files pin transitive dependencies that your direct package.json entries may not explicitly reference. A vulnerability in a nested dependency can be invisible until you scan the lock file directly.
Tools to integrate into your CI pipeline:
- pnpm audit — scans your pnpm lock file against the npm advisory database
- Orbis AppSec — automated PR-based fixes like the one that generated this post
- Dependabot / Renovate — automated dependency update PRs
- Snyk — deep dependency graph scanning with fix suggestions
4. Apply Backpressure When Piping Streams
Even with axios fixed, always handle backpressure and error events when piping:
const source = response.data;
const dest = fs.createWriteStream('/tmp/output');
source.on('error', (err) => {
dest.destroy();
// handle error
});
dest.on('error', (err) => {
source.destroy();
// handle error
});
source.pipe(dest);
5. Reference Standards
- CWE-400: Uncontrolled Resource Consumption — the root CWE for this vulnerability class
- OWASP A05:2021 – Security Misconfiguration (includes missing resource limits)
- OWASP A06:2021 – Vulnerable and Outdated Components (the direct category for this fix)
Key Takeaways
responseType: 'stream'bypassed axios'smaxContentLengthguard in versions before 1.15.1 — a guard that developers reasonably expected to protect them.- The
pnpm-lock.yamlfile is a security artifact, not just a reproducibility tool. Pinning axios at1.12.2locked in this vulnerability until an explicit upgrade was applied. - Upgrading from
1.12.2to1.15.1is the minimum safe version for any project that uses axios with streaming responses — partial version bumps within1.12.xdo not contain this fix. - Slow-stream DoS attacks are low-bandwidth and hard to detect with traditional rate limiting — they require application-level resource caps to mitigate effectively.
- Explicit timeout and content-length configuration in axios is now a best practice, not optional hardening, especially for any service that fetches data from external or user-controlled URLs.
Conclusion
CVE-2026-42036 is a reminder that even well-trusted libraries like axios can harbor subtle resource management flaws. The combination of a widely-used responseType: 'stream' pattern and a missing enforcement boundary in versions up to 1.12.2 created a genuine Denial of Service risk for any Node.js application that fetches external data. The upgrade to 1.15.1 — a single line change in pnpm-lock.yaml — closes that gap by bringing stream responses under the same resource controls that buffered responses have always had.
Keep your lock files audited, configure explicit resource limits on all HTTP clients, and treat dependency upgrades as security patches — not just feature updates.
This vulnerability was automatically detected and remediated by Orbis AppSec. Automated security fixes help teams stay ahead of dependency vulnerabilities without waiting for manual review cycles.