Integrity Failures in Express with Hmac Signatures
Integrity Failures in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
In Express, an integrity failure occurs when a server uses HMAC signatures to verify the authenticity of incoming data but does so in a way that does not reliably protect against tampering. A common pattern is to compute a signature on the client side using a shared secret and send it in a header, such as x-api-signature, while the server recomputes and compares the signature. If the comparison is performed incorrectly—typically by using a non-constant-time string comparison—an attacker can exploit timing differences to learn information about the expected signature and mount a signature validation bypass.
Express applications that accept JSON or form-encoded bodies and then sign only a subset of the fields (or the raw body) without canonicalization are also at risk. For example, if the client signs the JSON string {"action":"transfer","amount":100} and the server receives the same logical payload but with reordered keys as {"amount":100,"action":"transfer"}, the byte-level representations differ, causing the HMAC verification to fail or, in unsafe implementations, to be bypassed. This is an integrity failure because the server may process the reordered-payload request as valid when it should be rejected.
Another realistic scenario involves missing or weak secret management. If the shared HMAC secret is embedded in source code, stored in environment variables that are accidentally logged, or transmitted over insecure channels during deployment, an attacker who gains access to the secret can forge valid signatures. Without proper secret rotation and access controls, any integrity protection provided by HMAC collapses because an attacker can generate valid signatures for malicious requests.
Middleware that logs or echoes the received signature or the computed MAC without care can also contribute to integrity failures by leaking information through logs or error messages. If an attacker can observe whether a signature is considered valid indirectly—through timing, logs, or error responses—they may infer the correct signature or secret over time. Additionally, failing to enforce signature presence on all state-changing endpoints allows some requests to bypass integrity checks entirely, undermining the protection that HMAC is meant to provide.
These integrity issues map to common attack patterns such as tampering with API requests to escalate privileges or perform unauthorized actions. For example, an attacker might modify the amount field in a financial transaction or change the role field in an administrative request if the server does not enforce strict signature verification and input integrity. Because HMAC relies on a shared secret and an agreed encoding, any inconsistency between client and server in how data is serialized and signed creates an integrity failure that can be exploited in real attacks.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
To remediate integrity failures with HMAC signatures in Express, ensure canonical serialization, constant-time comparison, and strict validation on every request. Below is a minimal, secure example using the crypto module to compute and verify HMAC-SHA256 signatures over a canonical JSON representation.
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());
const SHARED_SECRET = process.env.HMAC_SECRET; // must be long, random, and stored securely
function canonicalStringify(obj) {
// Deterministic JSON serialization to prevent key-order mismatches
if (obj === null || typeof obj !== 'object') {
return JSON.stringify(obj);
}
if (Array.isArray(obj)) {
return '[' + obj.map(canonicalStringify).join(',') + ']';
}
const keys = Object.keys(obj).sort();
const pairs = keys.map((k) => {
const v = obj[k];
// Ensure nested objects are canonicalized recursively
return JSON.stringify(k) + ':' + canonicalStringify(v);
});
return '{' + pairs.join(',') + '}';
}
function computeSignature(body) {
const canonical = canonicalStringify(body);
return crypto.createHmac('sha256', SHARED_SECRET).update(canonical, 'utf8').digest('hex');
}
app.post('/transfer', (req, res) => {
const receivedSignature = req.get('x-api-signature');
if (!receivedSignature) {
return res.status(400).json({ error: 'Missing signature' });
}
const expectedSignature = computeSignature(req.body);
// Constant-time comparison to avoid timing attacks
const isValid = crypto.timingSafeEqual(
Buffer.from(receivedSignature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Proceed with business logic only after integrity verification
res.json({ status: 'ok' });
});
app.listen(3000, () => console.log('Server running on port 3000'));
Key practices reflected in this remediation:
- Canonical serialization: Use a deterministic string representation (sorted keys, consistent formatting) so equivalent logical payloads always produce the same bytes.
- Constant-time comparison: Use
crypto.timingSafeEqualwithBufferto prevent attackers from learning partial signature information through timing differences. - Mandatory signature presence: Reject requests that do not include the signature header rather than allowing fallback to unverified processing.
- Secure secret handling: Store the HMAC secret in environment variables managed by a secure runtime, never in source code, and rotate it periodically.
If you use the CLI, you can scan an Express endpoint to verify that your HMAC implementation is correctly enforced: middlebrick scan http://localhost:3000/transfer. The scan will highlight missing signature requirements or inconsistent validation logic as actionable findings. For teams that want continuous assurance, the Pro plan includes continuous monitoring and can integrate with your CI/CD pipeline via the GitHub Action to fail builds if risk scores degrade.