Api Key Exposure in Strapi with Mutual Tls
Api Key Exposure in Strapi with Mutual Tls — how this specific combination creates or exposes the vulnerability
API key exposure in Strapi when mutual TLS (mTLS) is used can occur when authentication controls are misaligned with transport-layer guarantees. Strapi, by default, does not enforce client certificate validation for every incoming request. If mTLS is handled at a reverse proxy or load balancer and the backend accepts requests without verifying that the client presented a valid certificate, an API key exposed in logs, error messages, or client-side JavaScript can be used outside the intended mTLS context.
Consider a scenario where an OpenAPI spec for a Strapi plugin defines an endpoint /api/reports/:id that requires an X-API-Key header. The spec may indicate the endpoint uses mTLS, but if Strapi’s server configuration does not enforce client certificate validation, an attacker who obtains the API key (for example, via a source code leak) can call the endpoint from any TLS client. The mTLS channel is then bypassed, and the API key becomes the sole gatekeeper.
Additionally, if Strapi returns stack traces or verbose errors in development mode, an API key sent in headers might be reflected in logs or error payloads. When mTLS is used but not strictly required for all routes, an attacker could probe unauthenticated paths, identify which endpoints consume the API key, and craft requests that appear authorized due to the presence of the key alone.
Another vector involves integration with external services. If Strapi makes outbound requests to third-party APIs using an API key and does not enforce mTLS on those outbound calls, the key may be transmitted over insecure channels. Even if inbound mTLS is configured, missing outbound enforcement can lead to key exposure in network traces or compromised intermediaries.
Operational practices can compound the risk. For example, using the same API key across multiple environments (staging and production) while relying on mTLS only in production increases the attack surface. If a developer accidentally deploys a configuration that disables client certificate verification, the API key is effectively exposed to any client that can reach the endpoint.
To detect this class of issues, scans should cross-reference the OpenAPI specification’s security schemes with runtime behavior. middleBrick compares the spec-defined security requirements — such as securitySchemes with mutualTls or apiKey` in `header` — against actual endpoint responses. It checks whether endpoints that declare mTLS also validate client certificates and whether API keys are accepted over channels that do not meet the declared transport guarantees.
Mutual Tls-Specific Remediation in Strapi — concrete code fixes
Remediation centers on ensuring Strapi enforces client certificate validation for all relevant routes and that API keys are only accepted over verified mTLS channels. Below are concrete configuration and code examples.
1. Enforce client certificate validation in Strapi’s server setup
Strapi uses an underlying HTTP server that can be configured to require client certificates. In a typical Node.js HTTPS server setup (e.g., when using a custom server entry), you can pass requestCert and rejectUnauthorized options.
const fs = require('fs');
const https = require('https');
const { createServer } = require('http');
const { app } = require('./src/server'); // Strapi instance
const options = {
key: fs.readFileSync('/path/to/server-key.pem'),
cert: fs.readFileSync('/path/to/server-cert.pem'),
ca: fs.readFileSync('/path/to/ca-cert.pem'),
requestCert: true,
rejectUnauthorized: true, // Enforce client certificate validation
};
https.createServer(options, app.callback()).listen(1337, () => {
console.log('Strapi server with mTLS running on port 1337');
});
This configuration ensures that any client without a valid, trusted certificate is rejected before Strapi processes the request.
2. Validate client certificates at the route level (middleware)
If you cannot enforce mTLS at the server level, add a custom middleware in Strapi to inspect the request socket’s verified certificate.
// src/middlewares/mtls-validation/config.js
module.exports = {
settings: {},
};
// src/middlewares/mtls-validation/middleware.js
module.exports = (config, { strapi }) => {
return async (ctx, next) => {
const socket = ctx.req.socket;
const authorized = socket.authorized || (socket.client.authorized && socket.client.authorized === true);
if (!authorized) {
ctx.status = 401;
ctx.body = { error: 'Unauthorized: client certificate required' };
return;
}
// Optionally inspect certificate fields
const cert = socket.getPeerCertificate();
if (!cert || Object.keys(cert).length === 0) {
ctx.status = 401;
ctx.body = { error: 'Unauthorized: no client certificate provided' };
return;
}
await next();
};
};
Register this middleware in src/middlewares/mtls-validation/index.js and apply it to routes that handle API keys.
3. Ensure API keys are only accepted over mTLS channels
When using API keys in headers, add a check that the request was authenticated via mTLS before allowing access to sensitive endpoints.
// Example: protect an endpoint in a Strapi controller
module.exports = {
async find(ctx) {
const socket = ctx.req.socket;
if (!socket.authorized) {
return ctx.unauthorized('Request must use mutual TLS');
}
// Proceed with business logic
const reports = await strapi.entityService.findMany('api::report.report', {
filters: {},
});
return reports;
},
};
4. Audit and rotate API keys in combination with mTLS
Even with mTLS, treat API keys as secrets. Rotate them regularly and avoid embedding them in client-side code. Use environment variables and restrict key permissions to the least privilege necessary.
5. Verify outbound calls
If Strapi makes calls to other APIs using an API key, enforce mTLS on those outbound requests using a custom HTTP agent or by configuring the HTTP client (e.g., axios) with a client certificate.
const axios = require('axios');
const https = require('https');
const fs = require('fs');
const agent = new https.Agent({
cert: fs.readFileSync('/path/client-cert.pem'),
key: fs.readFileSync('/path/client-key.pem'),
ca: fs.readFileSync('/path/ca-cert.pem'),
rejectUnauthorized: true,
});
axios.get('https://external-service/api/data', { httpsAgent: agent })
.then(response => console.log(response.data))
.catch(error => console.error(error));