Lack of Application Resilience: Limiting Work and Planning Recovery

The OWASP Top 10:2025 has a Next Steps section for risks that sit outside the ten selected categories. I'm continuing these notes with the three entries in that section, starting with X01:2025, Lack of Application Resilience.
OWASP renamed the earlier Denial of Service entry to focus on the weaknesses that allow a failure to happen. The Next Steps guidance connects resilience to the application's ability to withstand stress and recover safely.
The question for this entry is: how much work can one request cause, and what happens when the system cannot keep up?
A small request can create a large amount of work
Consider a hypothetical service that accepts an archive and generates a report from its contents. Checking the uploaded file's size is useful, but the compressed size does not tell the service how much data it will need to process after extraction.
A relatively small archive can expand into enough data to exhaust memory or storage. This is the problem described by CWE-409: Improper Handling of Highly Compressed Data.
For this feature, useful limits would include the total expanded size, the number of entries, nesting depth, processing time, and concurrent jobs. Those limits need to be enforced during processing, before the service has already consumed the resources it was supposed to protect.
A report generator provides another example. A short request might ask for years of records and an expensive export. The request looks ordinary, but its cost depends on the work behind it.
Put limits where the work happens
Rate limits help control how often an operation is requested. They work alongside limits on what each operation may consume.
A service can limit request size, query scope, response size, execution time, queue length, and concurrent work. Different features may need different limits. An inexpensive account lookup and a large export should not automatically receive the same resource budget.
The OWASP Denial of Service Cheat Sheet recommends examining expensive features and rejecting unsuitable input before doing more costly processing.
Moving work into a background queue can free a request thread, but the queued work still consumes resources. The queue needs a capacity limit and a defined response when it is full. Otherwise, the application has moved the growing backlog somewhere else.
Keep a dependency failure contained
Imagine a customer page that calls a separate reporting service. If every slow report holds a connection from the same pool used by other features, a reporting problem may eventually prevent unrelated requests from completing.
The bulkhead pattern separates resource pools so that one workload or dependency cannot consume everything another needs. A circuit breaker addresses a related problem by temporarily stopping calls to a dependency that is repeatedly failing.
Both need deliberate configuration and testing. Isolation has a cost, and a circuit breaker needs a sensible way to resume traffic when the dependency recovers.
Graceful degradation also needs boundaries. A page might omit an optional report while keeping account information available. A failed authorization check still needs to withhold protected data.
Retries can make an outage worse
Retrying a temporary failure can help, but repeated attempts also add load. If several layers retry independently, a single request can produce many downstream attempts.
Retries need a limit, suitable delays, and a total time budget. Adding jitter spreads attempts out so that clients do not all retry together. Operations with side effects also need a way to avoid applying the same change twice when the first result is uncertain. Amazon's guidance on timeouts, retries, and backoff with jitter explains these trade-offs.
Test recovery as well as rejection
For the archive example, an authorized test plan could check that processing stops at the configured limit, temporary files are cleaned up, and ordinary requests remain usable. A separate test could make the reporting dependency slow and then restore it, checking whether the backlog clears and normal service returns.
These are proposed checks, not results from a lab session. Load and failure testing need an agreed environment, scope, and stopping conditions.
Questions for my notes
- Which operations are expensive, even with small inputs?
- What limits apply to each request and to total concurrent work?
- Can one tenant or dependency consume resources needed by others?
- Do retries increase the load during a failure?
- How will we know the application has recovered?
Resilience connects application behavior to capacity and recovery. A useful starting point is one expensive feature: identify its limits, decide what happens when they are reached, and test how normal operation returns.
The next entry looks at Memory Management Failures, including the native components underneath a web application.
Sources and further reading
Building a SaaS? Explore my independent security reviews