Format String in Express with Api Keys
Format String in Express with Api Keys — how this specific combination creates or exposes the vulnerability
A format string vulnerability in an Express route that handles API keys occurs when user-controlled input is passed directly to a formatting function such as console.log, util.format, or a logging library without a proper format string. For example, if an endpoint accepts an API key via query or header and then logs it using a vulnerable pattern, an attacker can supply format specifiers like %s, %j, or %n that cause the function to read or write from the stack. In the context of API keys, this can lead to leaking other request data, memory contents, or in some configurations, enabling code execution through the injected format directives.
Consider an Express route that logs an API key received in a header:
const express = require('express');
const app = express();
app.get('/resource', (req, res) => {
const apiKey = req.header('X-API-Key');
// Vulnerable: user input used directly as the format string
console.log(apiKey);
res.send('ok');
});
If the logging utility or console.log implementation in the runtime applies formatting (some do when the first argument contains %), an attacker can send a crafted API key such as AAAA'%s%s%s%s%s to cause additional stack values to be printed. This may expose parts of the API key, other headers, or internal state. More dangerous patterns occur when developers mistakenly treat the API key as a format string, for example:
const userKey = req.header('X-API-Key');
util.format(userKey);
Because util.format expects a format string as its first argument, if userKey contains specifiers, it can read arbitrary arguments passed to util.format or trigger side effects. In an Express app, this can happen inadvertently when combining logging, error messages, or dynamic responses with user-controlled API key values. An attacker may also leverage this to cause denial of service by exhausting resources or, in environments where the runtime is particularly susceptible, achieve more severe outcomes.
The risk is compounded when API keys are logged for debugging or audit purposes without sanitization. An attacker can send a key like AAAA%j to attempt to serialize adjacent memory as JSON, potentially leaking sensitive data from the process. Because API keys are high-value secrets, any inadvertent disclosure through format string mistakes can aid further attacks, such as credential replay or token theft.
Api Keys-Specific Remediation in Express — concrete code fixes
To prevent format string issues when working with API keys in Express, always treat user input as data and never as a format string. Use explicit, safe logging and string construction, and avoid functions that interpret format specifiers on untrusted input.
1. Use a logging library that does not apply formatting to user-controlled fields, or pass the API key as a separate data argument rather than embedding it in the format string. For example, with console.log, simply concatenate or use structured logging:
const express = require('express');
const app = express();
app.get('/resource', (req, res) => {
const apiKey = req.header('X-API-Key');
// Safe: no format specifiers interpreted from apiKey
console.log('API key received, length:', apiKey ? apiKey.length : 0);
// Avoid printing the raw key in logs; if necessary, mask it
const masked = apiKey ? apiKey.substring(0, 4) + '****' : 'none';
console.log('Masked key:', masked);
res.send('ok');
});
2. If you must produce a formatted message, use a dedicated logging formatter and pass the API key as a substitution argument. For example, with Node’s util.format, ensure the format string is hard‑coded and the API key is provided as a separate parameter:
const util = require('util');
const express = require('express');
const app = express();
app.get('/resource', (req, res) => {
const apiKey = req.header('X-API-Key');
const message = util.format('API key received, masked: %s', apiKey ? '****' : 'none');
console.log(message);
res.send('ok');
});
3. Validate and sanitize API key values before any logging or processing. Reject malformed keys early and avoid reflecting user input in responses or logs:
const express = require('express');
const app = express();
function isValidApiKey(key) {
// Example: only allow alphanumeric of a fixed length
return typeof key === 'string' && /^[A-Za-z0-9]{32}$/.test(key);
}
app.get('/resource', (req, res) => {
const apiKey = req.header('X-API-Key');
if (!isValidApiKey(apiKey)) {
return res.status(400).send('Invalid API key');
}
// Safe: key is validated and logged without formatting
console.log('Valid API key received, first 4 chars:', apiKey.substring(0, 4));
res.send('ok');
});
4. For production, prefer structured logging with a library that supports safe serialization, and ensure that any debug or error paths do not use user input as format strings. If you use a logger that supports printf-style formatting, always supply a static format string and pass the data separately.
| Approach | Risk | Recommendation |
|---|---|---|
| User input as format string | High — can read/write memory | Never use raw API key as format string |
| Hard-coded format string + safe substitution | Low | Use util.format with a static format |
| Structured logging of metadata only | Low | Log length, timestamps, masked values |