Attack‑Tree‑Driven Rate‑Limiting After a DDoS
We just survived a 2026 DDoS that hammered our public REST gateway. A botnet slammed a handful of endpoints with insane request rates, chewing through CPU and memory until real users hit time‑outs. The fallout was a frantic review of our throttling logic. Attack trees turned out to be the perfect tool to decide which endpoints need the tightest controls and how to enforce them without turning our API into a brick wall.
Why Attack Trees?
An attack tree is a diagram that starts with a top‑level goal—say, “Prevent API outage due to traffic overload”—and branches into tactics, techniques, and procedures. Each leaf is a concrete attack step. By scoring nodes for likelihood, impact, and cost, you get a clear, data‑driven way to prioritize defenses.
For API defenses this is handy because:
- Granularity – You can drill down to specific endpoints, HTTP verbs, or even query strings.
- Quantification – Scores give you a risk‑adjusted budget for rate limits.
- Traceability – Every mitigation can be tied back to a threat, easing audits and compliance.
NIST’s CSF even recommends threat modeling as part of the “Identify” function, and attack trees fit that perfectly.
Building the Tree
- Root Goal – “Prevent API outage due to traffic overload.”
- Major Vectors –
- Botnet‑driven request floods
- Credential‑stressed brute‑force
- Denial‑of‑service via resource‑heavy endpoints
- Sub‑Goals – For the flood vector, examples:
- “Send >10 k req/s to
/orders/create” - “Send >5 k req/s to
/auth/login”
- “Send >10 k req/s to
- Scores –
- Likelihood: from logs & threat intel
- Impact: SLA breaches & revenue loss
- Cost: dev effort, infra, user friction
- Prioritize – Risk = likelihood × impact; tackle the highest first.
A compact tree for our case looks like this:
Root: API Availability
├─ Flood Botnet
│ ├─ /orders/create >10k req/s (Risk 9)
│ ├─ /auth/login >5k req/s (Risk 7)
│ └─ /search?q=* >8k req/s (Risk 5)
├─ Credential Stuffing
│ ├─ /auth/login >1k req/s (Risk 6)
│ └─ /reset-password >500 req/s (Risk 4)
└─ Resource Exhaustion
├─ /reports/generate >200 req/s (Risk 8)
└─ /export/data >150 req/s (Risk 6)
Translating Scores into Rate Limits
| Endpoint | Current | New Target | Rationale |
|---|---|---|---|
/orders/create |
100/min | 50/min | Highest risk; 10 k req/s seen. |
/auth/login |
200/min | 120/min | Brute‑force + flood. |
/search |
500/min | 300/min | High volume, lower risk. |
/reports/generate |
50/min | 30/min | Resource‑heavy, moderate risk. |
/export/data |
30/min | 20/min | Similar to reports. |
Limits are per IP by default, but we also layered per API key and per user throttles where it made sense. The trade‑off? A modest latency bump for real users during peaks, but the chance of a full outage is gone.
Implementation Snippets
NGINX Rate Limiting
# Shared memory zone
limit_req_zone $binary_remote_addr zone=api:10m rate=50r/m;
# Specific location
location /orders/create {
limit_req zone=api burst=10 nodelay;
proxy_pass http://backend;
}
Envoy Filters
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: api-rate-limit
spec:
workloadSelector:
labels:
app: api-gateway
configPatches:
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: envoy.http_connection_manager
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
domain: api
failure_mode_deny: true
rate_limit_service:
grpc_service:
envoy_grpc:
cluster_name: ratelimit_cluster
Redis‑Backed Token Bucket (Node.js)
const Redis = require('ioredis');
const redis = new Redis();
async function allowRequest(key, limit, windowMs) {
const now = Date.now();
const windowStart = now - windowMs;
const count = await redis.zcount(key, windowStart, now);
if (count >= limit) return false;
await redis.zadd(key, now, now);
await redis.expire(key, Math.ceil(windowMs / 1000));
return true;
}
Practical Caveats
| Issue | Mitigation |
|---|---|
| False positives – Legit high‑volume clients hit limits. | Whitelists per API key/IP and a dynamic admin UI to tweak thresholds. |
| Distributed attacks – Botnet spans many IPs. | Combine IP limits with user‑agent, API key checks, or client‑cert throttling for internal services. |
| Latency spikes – Burst handling can cause jitter. | Tune burst parameters; only enable nodelay on critical endpoints. |
| Monitoring blind spots – Logs may miss abuse patterns. | Instrument with Prometheus; alert on sudden 429 spikes. |
| Compliance – Need audit trails of throttling decisions. | Log decisions to a tamper‑evident store (e.g., write‑once storage). |
Post‑Implementation Review
After rollout:
/orders/createsaw a 30 % drop in peak traffic.- 429 responses jumped from 0.1 % to 2 %, but no 5xx errors appeared.
- Support tickets fell 15 % and no complaints surfaced.
We also ran a staged re‑attack with a controlled botnet; the new limits held and the backend stayed responsive.
Lessons Learned
- Attack trees give you numbers – No more guessing which endpoint to throttle; you base it on measurable risk scores.
- Rate limits aren’t one‑size‑fits‑all – A blanket cap hurts usability; tailor controls per endpoint.
- Dynamic adjustment is essential – Threats shift; re‑score the tree weekly and adjust limits.
- Documentation matters – Keep the attack tree and mitigation matrix in a shared repo so new folks can see the rationale.
Further Reading
- OWASP API Security Project – Rate‑Limiting Best Practices: https://owasp.org/www-project-api-security/
- NIST SP 800‑61 Rev. 2 – Incident Response Guide (Section on DoS mitigation): https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-61r2.pdf
See also
- Beyond “Null Pointer” – A Pragmatic Guide to Detecting and Fixing Hidden Buffer Overflows in Rust‑Based Web Services
- When a Third‑Party Binary Gets Stuck in Your CI Pipeline: Spotting and Removing Malicious Artifacts Before Deployment
- When Your CI Pipeline Ignores Artifact Signatures: How to Enforce GPG Checks Before Deploying to Production
- How to Spot and Block Unauthorized Package Signing in Your CI/CD Pipeline
- Tuning SIEM Correlation Rules to Cut False Positives on Azure AD Sign‑Ins