Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Web Security (OWASP Top 10:2025)  Module 11 – A10:2025 Mishandling of Exceptional Conditions

Resource exhaustion, limits and ReDoS

Stop requests from using more time, memory and connections than they deserve: size and time limits, regex backtracking, decompression bombs and status codes.

  • Intermediate
  • 30 minutes
  • Examples run with Node.js 24.21.0, quickjs 0.32.0, Python 3.14.8, Pyodide 314.0.7 and sql.js 1.14.2
  • By MySmartCoPilot
Free intro, Pro: 5 of 30 minutes free
Free: Every limit is a decision, and the runtime's defaults leave several of them undecided. With a pass: the other 7 sections and 3 interview answers, about 25 minutes.

What you will learn

  • List the limits every service needs: body and header size, JSON depth, page size, timeouts, concurrency and archive sizes
  • Configure limits in Node.js and Python servers and in a reverse proxy, and say which defaults leave a limit unset
  • Explain catastrophic backtracking and fix a vulnerable regular expression with a rewrite, a cap or a linear-time engine
  • Prevent compressed and deeply nested input from causing more work than it deserves, before it is unpacked or parsed
  • Answer oversized and overloaded requests with the right 413, 414, 429, 431 or 503 status, and keep other endpoints serving

Before you start

What you’ll learn · Before you start

What you will learn

  • List the limits every service needs: body and header size, JSON depth, page size, timeouts, concurrency and archive sizes
  • Configure limits in Node.js and Python servers and in a reverse proxy, and say which defaults leave a limit unset
  • Explain catastrophic backtracking and fix a vulnerable regular expression with a rewrite, a cap or a linear-time engine
  • Prevent compressed and deeply nested input from causing more work than it deserves, before it is unpacked or parsed
  • Answer oversized and overloaded requests with the right 413, 414, 429, 431 or 503 status, and keep other endpoints serving

Before you start

On this page

A request that costs the server 400 milliseconds is harmless. A thousand of them in a minute are not, and neither is one text that makes a pattern run for an hour. In this lesson’s model, a regular expression with nested quantifiers needs about 540 million tries on 30 characters, and more than an hour on 40.

OWASP’s A10:2025 says that nothing in information technology should be limitless. This lesson covers resource exhaustion, the limits that prevent it, and ReDoS, a regular expression that takes exponentially longer as its input grows, and it ends with an exercise on hostile input.

Every limit is a decision, and the runtime’s defaults leave several of them undecided

resource exhaustion: A condition in which requests use up something the service shares, such as CPU time, memory, connections, disk or a paid quota, so that it slows down or stops for everyone. CWE-770 names the missing limits behind it. happens when requests use up something the service shares: CPU time, memory, connections, disk or a paid quota. A bug, a retry loop or one large customer causes it as easily as an attacker does. CWE-770 names the cause, allocation of resources without limits or throttling.

Limits are numbers fixed before the request arrives: how much of a resource one request, one user or one kind of work may use. They matter most near capacity, where a server slows down fast. Move the arrivals of this single-queue model from 90 to 99 a second:

Interactive · change it and see Time in the system for a server that handles 100 requests a second Requests arrive at random at an average rate and one server handles them one at a time. Raise the arrival rate towards the service rate and watch the time in the system.
Mean time in the system by utilisation: mean time in the system (ms) against utilisation ρ = λ/μ (%).020406080100050100150utilisation ρ = λ/μ (%)mean time in the system (ms)time in systemservice alone 10 msnow 90 %

At 90 % utilisation a request spends 100 ms in the system on average, 10 times its 10 ms of service: the rest is waiting. Little's law: L = λW = 9 requests in the system.

Mean time in the system (W) 100 ms
Utilisation (ρ) 90 %
Mean wait before service 90 ms
Mean number in the system (L = λW) 9
The numbers as a table
Mean time in the system with μ = 100 a second
UtilisationMean time in the system
50 %20 ms
70 %33.3 ms
80 %50 ms
90 %100 ms
95 %200 ms
99 %1,000 ms

At 90 arrivals a second the average request takes 100 ms, ten times its 10 ms of service, and at 99 it takes 1,000 ms, a hundred times. A plain Node.js server decides some limits and leaves others to you. This program reads them from a server that is never started, then reads a body of three megabytes with and without a count:

The limits of a plain Node.js server, and a body read with and without a count JavaScript · defaults/node_defaults.mjs
// The limits of a plain Node.js HTTP server, read from a server object that is never started, and what happens to a
// request body when nothing counts it.
import http from 'node:http';
import { Readable } from 'node:stream';

const server = http.createServer();
const limits = [
  ['server.headersTimeout', server.headersTimeout, 'ms to receive all the headers of a request'],
  ['server.requestTimeout', server.requestTimeout, 'ms to receive the whole request'],
  ['server.keepAliveTimeout', server.keepAliveTimeout, 'ms an idle connection is kept for the next request'],
  ['server.timeout', server.timeout, 'ms of socket inactivity (0: never)'],
  ['server.maxRequestsPerSocket', server.maxRequestsPerSocket, 'requests on one connection (0: no limit)'],
  ['http.maxHeaderSize', http.maxHeaderSize, 'bytes of headers'],
];
for (const [name, value, meaning] of limits) console.log(`${name.padEnd(28)} ${String(value).padStart(7)}  ${meaning}`);
console.log('A setting for the size of a request body: none. The server hands your code a stream.\n');

// A body of 3 chunks of 1 MiB arrives. The first handler reads it all; the second counts as it reads.
const MiB = 1024 * 1024;
const body = () => Readable.from([Buffer.alloc(MiB), Buffer.alloc(MiB), Buffer.alloc(MiB)]);

async function readAll(stream) {
  let size = 0;
  for await (const chunk of stream) size += chunk.length;
  return size;
}

async function readUpTo(stream, limit) {
  let size = 0;
  for await (const chunk of stream) {
    size += chunk.length;
    if (size > limit) {
      stream.destroy();
      return { status: 413, bytesRead: size };
    }
  }
  return { status: 200, bytesRead: size };
}

console.log(`Without a limit the handler read ${(await readAll(body())) / MiB} MiB.`);
const stopped = await readUpTo(body(), MiB);
console.log(`With a limit of 1 MiB it answered ${stopped.status} after reading ${stopped.bytesRead / MiB} MiB.`);

Output

server.headersTimeout          60000  ms to receive all the headers of a request
server.requestTimeout         300000  ms to receive the whole request
server.keepAliveTimeout         5000  ms an idle connection is kept for the next request
server.timeout                     0  ms of socket inactivity (0: never)
server.maxRequestsPerSocket        0  requests on one connection (0: no limit)
http.maxHeaderSize             16384  bytes of headers
A setting for the size of a request body: none. The server hands your code a stream.

Without a limit the handler read 3 MiB.
With a limit of 1 MiB it answered 413 after reading 2 MiB.

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node node_defaults.mjs

Four limits have values: 60 s for headers, 300 s for a whole request, 5 s of keep-alive and 16 KiB of headers. The socket timeout and the requests per connection are off, and the body has no setting at all, because the server hands your code a stream. With a count, the 1 MiB limit stopped after 2 MiB, since it counts whole chunks. The rest of this lesson takes the limits one at a time:

  • A size limit refuses a body, header or URL above a number of bytes before it is read or parsed.
  • A shape limit caps nesting depth, item counts and string lengths in structured input.
  • A time limit ends a request or call that takes too long, so nothing is held for ever.
  • A concurrency limit caps how many requests of one kind run at once and sheds the rest with a 503.
  • A ReDoS defence is a rewrite of the pattern, a cap on the input, a linear-time engine or a deadline in an isolated worker.
  • A decompression limit caps the bytes that a compressed input may produce, so that a small upload cannot become a huge one.

Checkpoint

Check: Limits are decisions

  1. Question 1 of 2 A handler reads the whole request body into memory and then checks its length. What is wrong with that?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The whole body was already read, so the check should count bytes while reading and stop at the limit

    Count while reading, stop at the limit, and answer 413 before anything is parsed.

  2. Question 2 of 2 A server handles 100 requests a second, each in 10 ms. What is the average time in the system, in milliseconds, when 99 requests arrive a second?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 1000 ms

    The model gives 1 ÷ (100 − 99) = 1 second, which is 1,000 ms and 100 times the 10 ms of service. At 90 arrivals it is 100 ms.

Flashcards · 4 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What is resource exhaustion, and which CWE names its cause?

    Requests use up something the service shares: CPU time, memory, connections, disk or a paid quota. A bug, a retry loop or one large customer causes it as easily as an attacker does. CWE-770 is allocation of resources without limits or throttling, and OWASP's A10:2025 says nothing in information technology should be limitless.

  2. Why do limits matter most near capacity?

    A server slows down fast there. In the single-queue model with 10 ms of service, 90 arrivals a second gave an average request time of 100 ms, ten times the service time, and 99 arrivals a second gave 1,000 ms, a hundred times.

  3. Which limits does a plain Node.js server decide, and which does it leave to you?

    It has values for headers (60 s), a whole request (300 s), keep-alive (5 s) and header size (16 KiB). The socket timeout and the requests per connection are off, and the body has no setting at all, because the server hands your code a stream; a 1 MiB count that adds whole chunks stopped after 2 MiB.

  4. Name the six kinds of limit the lesson covers.

    A size limit (refuse a body, header or URL above a number of bytes before reading it); a shape limit (nesting depth, item counts, string lengths); a time limit; a concurrency limit (cap simultaneous requests of one kind and shed the rest with a 503); a ReDoS defence (rewrite the pattern, cap the input, a linear-time engine or a deadline in an isolated worker); and a decompression limit.

Every input has a size, a shape and a time budget Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Timeouts and concurrency: a slow request holds something
  • ReDoS: how one pattern turns 30 characters into half a billion tries
  • Small inputs that cause a lot of work: compressed streams and nested loops
  • Practise: cap the input, fix the pattern, bound the import
    • Cap the input, fix the pattern, bound the import
  • Interview questions
    • What is ReDoS, and which kind of regular expression is vulnerable to it?
    • After a release, the product search endpoint pins one CPU core for seconds on certain inputs, and the service falls behind. A profile shows the time inside a regular expression. How do you stop the incident, fix it properly, and make sure it cannot return?
    • A download-all-orders export takes 40 seconds. When three customers press it together, every other endpoint times out. Redesign the export and describe the limits around it.
    • You are adding a bulk-import endpoint that accepts gzip-compressed JSON with up to 5,000 rows. List the limits you set, where each is enforced, and what each refusal looks like.
  • Key takeaways

1 diagram · 7 runnable examples (JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 41 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Terms in this lesson

catastrophic backtracking
What a backtracking regular expression engine does on a pattern with several ways to match the same text: after a failure it tries every other way, and their number grows exponentially with the length of the input.
decompression bomb
A small compressed file that unpacks to a very large one, so that unpacking it fills memory or disk. A zip file's table of contents lists the sizes, so a program can refuse before it unpacks anything.
load shedding
Refusing some requests on purpose, usually with a 503 and a Retry-After header, when a service is near its limits, so that the requests it does accept still finish in time.
ReDoS
Regular expression denial of service: an input that makes a backtracking regular expression take exponentially longer as it grows, so that one request ties up a process for seconds or hours. CWE-1333 names the weakness.
resource exhaustion
A condition in which requests use up something the service shares, such as CPU time, memory, connections, disk or a paid quota, so that it slows down or stops for everyone. CWE-770 names the missing limits behind it.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.