HTTP requests without timeouts block indefinitely when a server stops responding without closing the TCP connection — a common failure mode in cloud environments during rolling restarts or network partitions. CWE-400 (uncontrolled resource consumption) applies: a single hung request holds a thread, connection slot, or serverless concurrency unit until the platform forcibly kills it — typically after 30+ seconds. At scale, simultaneous slow requests exhaust your connection pool entirely, making the application unresponsive for all users.
Medium because requests without timeouts deplete connection pools and serverless concurrency under partial network failures, causing application-wide unavailability.
Set explicit timeouts on every HTTP client. Use AbortController for native fetch; set timeout on axios instances. Never rely on platform-level request timeouts alone.
// lib/fetch.ts — fetch with timeout
export async function fetchWithTimeout(
url: string,
options: RequestInit = {},
timeoutMs = 10_000
): Promise<Response> {
const controller = new AbortController()
const id = setTimeout(() => controller.abort(), timeoutMs)
try {
return await fetch(url, { ...options, signal: controller.signal })
} finally {
clearTimeout(id)
}
}
Default to 10 seconds for user-facing requests, 30 seconds for background jobs. Document the timeout value alongside each integration so it's visible during code review.
ID: error-resilience.network-api-resilience.request-timeout
Severity: medium
What to look for: Count all HTTP client configurations and fetch calls. Enumerate which have explicit timeout values vs. which use no timeout (potentially hanging forever). Examine fetch calls, API client configuration (axios, ky, node-fetch, etc.), or middleware. Look for timeout settings on all HTTP requests.
Pass criteria: All HTTP requests have a timeout configured (typically 5-30 seconds); requests that exceed the timeout are aborted and handled as errors. At least 90% of HTTP requests must have explicit timeout values of no more than 30 seconds.
Fail criteria: Requests have no timeout, or timeout is not consistently configured across the application.
Skip (N/A) when: The application has no HTTP requests.
Cross-reference: For retry logic with backoff, see retry-logic-backoff.
Detail on fail: "No timeout configured on HTTP requests. Slow or unresponsive APIs will hang indefinitely" or "Timeout configured on some endpoints but not others (e.g., user-service has 10s timeout, payment-api has none)"
Remediation: Add timeouts to your HTTP client:
// lib/fetch.ts — fetch with timeout
const controller = new AbortController()
setTimeout(() => controller.abort(), 10000)
const res = await fetch(url, { signal: controller.signal })
// lib/api.ts
import axios from 'axios'
const api = axios.create({
timeout: 10000, // 10 seconds
baseURL: process.env.NEXT_PUBLIC_API_URL,
})
// Or with fetch:
async function fetchWithTimeout(url: string, timeout = 10000) {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)
try {
const response = await fetch(url, { signal: controller.signal })
return response
} finally {
clearTimeout(timeoutId)
}
}