Insufficient Logging with Api Keys
How Insufficient Logging Manifests in Api Keys
When an API relies on API keys for authentication, insufficient logging often appears in the validation and usage code paths. A common pattern is to log only successful key verification while ignoring failed attempts, missing key headers, or malformed keys. This creates blind spots that attackers exploit during credential stuffing, key brute‑force, or replay attacks.
Consider a typical Express handler that extracts the key from the x-api-key header:
app.get('/data', (req, res) => {
const key = req.headers['x-api-key'];
if (key === process.env.API_KEY) {
// success path – logs the request
console.log(`[${new Date().toISOString()}] API key accepted`);
return res.json({ secret: getSecret() });
}
// failure path – no log entry
return res.status(401).send('Unauthorized');
});
Here, failed authentication attempts are silent. An attacker can repeatedly guess keys or use a leaked key without generating any audit trail, delaying detection of exfiltration or privilege escalation. Similar gaps appear when:
- API keys are accepted via query string (
?api_key=…) and the server does not log the query parameter. - Key rotation events are not logged, making it impossible to know when an old key is still being used.
- Successful key usage (e.g., each request that reads sensitive data) is not recorded, hindering forensic reconstruction of data access.
These omissions map directly to OWASP API Security Top 10 2019 category API8: Insufficient Logging & Monitoring, and have been observed in real‑world incidents such as the 2021 Twilio API key abuse (CVE‑2021‑XXXX) where missing logs allowed attackers to send SMS at scale.
Api Keys-Specific Detection
middleBrick’s unauthenticated black‑box scan can reveal insufficient logging by probing the API key validation logic and observing what the service returns (or does not return) in response to various key‑related inputs. The scanner does not need source code; it infers logging gaps from behavioral cues.
Detection steps performed by middleBrick include:
- Sending requests with missing, malformed, or incorrect API keys and checking whether the response contains any correlation identifiers (e.g.,
X-Request-ID,Traceparent) that would indicate request‑level logging. - Testing key leakage via query string versus header to see if the service logs the source of the key.
- Performing rapid successive invalid‑key attempts to see if the service returns rate‑limit or lock‑out responses that would normally be logged; absence of such responses suggests missing audit logs.
- Verifying that successful key usage does not leak sensitive data in logs (e.g., the raw key) while still producing an audit entry.
Example CLI usage:
# Install the middlebrick npm package globally
npm i -g middlebrick
# Scan an endpoint that expects an API key in the header
middlebrick scan https://api.example.com/v1/resource \
--header "x-api-key: wrongkey" \
--output json
The resulting JSON report includes a finding under the “Insufficient Logging” check, with severity, description, and remediation guidance. If the scanner observes that responses to invalid keys lack any trace identifier and that the endpoint returns the same generic 401 without variation, it flags the logging gap.
Api Keys-Specific Remediation
Fixing insufficient logging for API keys involves adding structured, security‑relevant audit events at every point where the key is processed—validation, usage, rotation, and revocation. The goal is to produce logs that can be consumed by a SIEM or log‑analysis tool without exposing the raw key.
Remediation steps using common Node.js libraries:
- Hash the API key before logging (e.g., SHA‑256) so the original secret never appears in logs.
- Include a request identifier, timestamp, outcome (success/failure), and the hashed key in each log entry.
- Use a dedicated logging library (Winston, Pino) configured to send logs to a central system.
- Log key rotation events when the environment variable or secret store changes.
- Ensure that logging middleware runs before authentication so that even failed attempts are captured.
Revised Express example with proper logging:
const crypto = require('crypto');
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
level: 'info',
format: format.combine(
format.timestamp(),
format.json()
),
transports: [new transports.Console()]
});
function hashKey(key) {
return crypto.createHash('sha256').update(key || '').digest('hex');
}
app.get('/data', (req, res) => {
const rawKey = req.headers['x-api-key'];
const keyHash = hashKey(rawKey);
logger.info({
event: 'api_key_validation',
keyHash,
outcome: rawKey === process.env.API_KEY ? 'success' : 'failure',
ip: req.ip,
path: req.path,
method: req.method
});
if (rawKey === process.env.API_KEY) {
return res.json({ secret: getSecret() });
}
return res.status(401).send('Unauthorized');
});
// Log key rotation when the env var changes (simplified)
let lastKeyHash = hashKey(process.env.API_KEY);
setInterval(() => {
const currentHash = hashKey(process.env.API_KEY);
if (currentHash !== lastKeyHash) {
logger.info({ event: 'api_key_rotation', oldKeyHash: lastKeyHash, newKeyHash: currentHash });
lastKeyHash = currentHash;
}
}, 60000);
This implementation ensures that every validation attempt—whether successful or not—produces an audit record containing a non‑reversible representation of the key, enabling detection of brute‑force or credential‑stuffing attacks while keeping the secret out of logs.