Sandbox Escape in Feathersjs with Hmac Signatures
Sandbox Escape in Feathersjs with Hmac Signatures — how this specific combination creates or exposes the vulnerability
FeathersJS is a framework for real-time APIs that can use HMAC signatures to verify the integrity and origin of requests. A sandbox escape in this context occurs when an attacker bypasses intended isolation boundaries by leveraging weaknesses in how HMAC signatures are validated, allowing unauthorized access to internal services or administrative functionality. When HMAC signatures are used, the server typically computes a signature over selected parts of the request (such as the payload, timestamp, and a shared secret) and compares it to the value provided by the client.
Vulnerability arises when the implementation fails to enforce strict validation or allows ambiguous input to affect signature computation in unintended ways. For example, if the signature is computed over a subset of parameters but the server processes additional parameters before or after verification, an attacker may supply crafted data that changes the routing or service selection logic. In FeathersJS, services are often mounted at specific paths; if signature validation does not cover the full request path or if the service name is derivable from unverified input, an attacker can direct requests to unintended services.
Another vector involves timestamp or nonce handling. If the server accepts a wide time window for signature validity and does not enforce strict one-time use of nonces, an attacker may replay requests or shift the request context to access a different service endpoint that shares the same HMAC secret. Misconfigured service hooks or global middleware can further weaken isolation by allowing pre-authentication logic to influence which service is invoked, effectively enabling a sandbox escape across logical boundaries that should be distinct.
Consider a scenario where a FeathersJS app exposes multiple service routes and uses HMAC signatures only on the payload body but does not include the route or service name in the signed data. An attacker could send a POST intended for an internal administrative service by routing through a publicly exposed endpoint, relying on the server’s routing logic to map the request correctly while the signature appears valid. If the server performs signature verification after route resolution but applies permissive CORS or hook logic, the boundary between public and privileged services can be crossed.
Real-world patterns seen in integrations with OAuth or JWT prior to HMAC verification can also contribute to confusion. If the request includes both signed and unsigned identifiers, and the server uses the unsigned identifier to select a service while trusting the signed payload for authorization, the mismatch can lead to privilege escalation or access to internal endpoints. This is especially risky when debugging routes or versioned APIs are left accessible without corresponding HMAC coverage.
To detect such issues, scanning tools evaluate whether the HMAC scope includes all contextual elements that affect routing and service selection, whether timestamps and nonces are handled securely, and whether untrusted input can influence the resolution of internal endpoints. The presence of unauthenticated LLM endpoints or verbose error messages can further aid an attacker in mapping the service topology and refining an escape attempt.
Hmac Signatures-Specific Remediation in Feathersjs — concrete code fixes
Remediation centers on ensuring that HMAC signatures cover all data that influences routing, service selection, and authorization, and that validation occurs before any sensitive logic is executed. Below are concrete examples for a secure FeathersJS service using HMAC signatures.
Secure HMAC Verification Middleware
The following middleware computes the expected HMAC over the HTTP method, full path, timestamp, and the request body, and rejects the request if verification fails or if the timestamp is outside an acceptable window:
const crypto = require('node:crypto');
function verifyHmac(req, res, next) {
const signature = req.headers['x-hmac-signature'];
const timestamp = req.headers['x-request-timestamp'];
const secret = process.env.HMAC_SECRET;
if (!signature || !timestamp || !secret) {
return res.status(400).json({ message: 'Missing HMAC authentication headers' });
}
// Reject requests older than 5 minutes to prevent replay
const now = Date.now();
const requestTime = parseInt(timestamp, 10);
if (Math.abs(now - requestTime) > 5 * 60 * 1000) {
return res.status(401).json({ message: 'Request timestamp out of bounds' });
}
const payload = {
method: req.method,
path: req.originalUrl,
timestamp: timestamp,
body: req.body
};
const computed = crypto.createHmac('sha256', secret)
.update(payload.method + '|' + payload.path + '|' + payload.timestamp + '|' + JSON.stringify(payload.body))
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(computed))) {
return res.status(401).json({ message: 'Invalid HMAC signature' });
}
next();
}
Applying Middleware to a FeathersJS Service
Ensure the HMAC verification runs before the service handler and that the service name or route is included in the signed payload to prevent routing manipulation:
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const app = express(feathers());
// Apply HMAC verification globally before any service
app.use(verifyHmac);
app.use('/admin/users', {
async create(data, params) {
// Service logic here; by this point HMAC and routing integrity are verified
return { id: 1, ...data };
}
});
app.use('/public/items', {
async find(params) {
return [{ id: 1, name: 'public' }];
}
});
// Start server omitted for brevity
Include Path and Service Context in Signature
Signatures must incorporate the request path and, if applicable, the service identifier to ensure that an attacker cannot redirect a valid signature to a different route or service:
const payload = {
method: req.method,
path: req.originalUrl, // includes full route, e.g., /admin/users
service: 'admin-users', // explicit service identifier
timestamp: timestamp,
body: req.body
};
Additional Hardening Practices
- Use short timestamp windows (e.g., 1–5 minutes) and reject replayed timestamps.
- Ensure the HMAC secret is stored securely and rotated periodically.
- Avoid mixing signed and unsigned routing parameters that could lead to ambiguity.
- Return generic error messages for authentication failures to prevent information leakage.
These steps reduce the risk of sandbox escape by tightly binding the signature to the intended execution context and enforcing verification before routing decisions affect internal service selection.