CWE-538 in APIs
- CWE ID
- CWE-538
- Category
- Data Exposure
- Severity
- HIGH
- Short Name
- File Data Leak
What is CWE-538?
CWE-538 describes a scenario where an API endpoint or service becomes unavailable due to improper handling of client requests, resource exhaustion, or other operational failures. This weakness manifests when an API fails to maintain availability despite receiving legitimate traffic, often due to unhandled exceptions, resource leaks, or inadequate error handling.
In practical terms, CWE-538 occurs when an API endpoint that should remain accessible becomes unresponsive or returns errors to valid requests. This can happen through various mechanisms: uncaught exceptions that crash the service, memory leaks that eventually exhaust system resources, or infinite loops triggered by specific input patterns. The result is an API that cannot fulfill its intended function of providing reliable access to data or services.
CWE-538 in API Contexts
APIs face unique availability challenges compared to traditional web applications. An API endpoint experiencing CWE-538 might return HTTP 500 errors, hang indefinitely, or become completely unreachable. Common scenarios include:
- Database connection pool exhaustion from unclosed connections
- Memory leaks in request handlers that grow with each API call
- Uncaught exceptions in middleware that terminate the entire process
- Infinite loops triggered by malformed input or edge cases
- Resource exhaustion from unbounded data processing
Consider a REST API that processes file uploads. Without proper stream handling and cleanup, each upload could leak file handles or memory buffers. After hundreds of requests, the API might exhaust available file descriptors, causing all subsequent requests to fail regardless of their validity.
Detection
Detecting CWE-538 requires systematic testing of API availability under various conditions. Manual testing involves sending repeated requests to API endpoints while monitoring response times, error rates, and system resource usage. Look for patterns where availability degrades over time or under specific conditions.
Automated scanning tools like middleBrick can identify availability issues through black-box testing. The scanner sends repeated requests to API endpoints and analyzes response patterns for signs of instability. middleBrick specifically tests for:
- Response consistency across multiple requests
- Timeouts and hanging requests
- Error rate increases under load
- Resource exhaustion indicators
The scanner provides a security risk score (A–F) that includes availability assessment, helping developers identify endpoints that may become unavailable under certain conditions. For APIs with availability issues, middleBrick generates actionable findings with severity levels and remediation guidance.
Remediation
Fixing CWE-538 availability issues requires both immediate patches and architectural improvements. Here are code-level fixes with examples:
// Problematic: Unhandled promise rejection can crash the API
app.get('/api/data', async (req, res) => {
const data = await database.query('SELECT * FROM users');
res.json(data);
});
// Fixed: Proper error handling with graceful degradation
app.get('/api/data', async (req, res) => {
try {
const data = await database.query('SELECT * FROM users');
res.json(data);
} catch (error) {
console.error('Database query failed:', error);
res.status(500).json({
error: 'Service temporarily unavailable',
retryAfter: 60
});
}
});
Resource management is critical for preventing availability issues:
// Problematic: Potential memory leak with file uploads
app.post('/api/upload', (req, res) => {
const files = [];
req.on('data', chunk => {
files.push(chunk); // Accumulates in memory
});
req.on('end', () => {
const fileData = Buffer.concat(files);
processFile(fileData);
res.send('Upload complete');
});
});
// Fixed: Stream processing with cleanup
app.post('/api/upload', (req, res) => {
const fileStream = fs.createWriteStream('/tmp/upload.bin');
req.pipe(fileStream);
req.on('end', () => {
fileStream.close();
processFile('/tmp/upload.bin');
res.send('Upload complete');
});
req.on('error', error => {
fileStream.destroy();
res.status(500).send('Upload failed');
});
});
Connection pooling prevents database-related availability issues:
// Problematic: Creating new connections per request
app.get('/api/users', async (req, res) => {
const connection = await db.createConnection();
const users = await connection.query('SELECT * FROM users');
connection.close();
res.json(users);
});
// Fixed: Using connection pool
const pool = new Pool({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
maxConnections: 10
});
app.get('/api/users', async (req, res) => {
try {
const client = await pool.connect();
const users = await client.query('SELECT * FROM users');
client.release();
res.json(users);
} catch (error) {
res.status(500).json({ error: 'Service unavailable' });
}
});
Implementing circuit breakers prevents cascading failures:
const CircuitBreaker = require('opossum');
const dbBreaker = new CircuitBreaker(async () => {
return await database.query('SELECT * FROM users');
}, {
timeout: 5000,
errorThresholdPercentage: 50,
resetTimeout: 30000
});
dbBreaker.on('open', () => {
console.log('Database circuit breaker opened');
});
dbBreaker.on('halfOpen', () => {
console.log('Database circuit breaker half-open');
});
app.get('/api/users', async (req, res) => {
try {
const users = await dbBreaker.fire();
res.json(users);
} catch (error) {
res.status(503).json({
error: 'Service unavailable',
retryAfter: 30
});
}
});