Container Escape in Strapi with Bearer Tokens
Container Escape in Strapi with Bearer Tokens — how this specific combination creates or exposes the vulnerability
A container escape in Strapi combined with bearer token authentication can amplify the impact of an API security misconfiguration. Strapi, as a headless CMS, often exposes REST or GraphQL endpoints that rely on bearer tokens for access control. When authorization logic incorrectly trusts the presence of a token without validating scope, ownership, or tenant boundaries, an attacker who obtains or guesses a valid token may traverse API routes that should be isolated.
Consider an endpoint like GET /api/containers/:id that returns configuration details for a container. If Strapi applies middleware that only checks for a valid bearer token and then uses the :id parameter to query the database without ensuring the container belongs to the authenticated actor’s organization, an attacker can enumerate or manipulate IDs to access containers they should not see. In a containerized deployment, this may expose internal endpoints, service accounts, or metadata routes that lead to further compromise.
In more severe cases, misconfigured role-based access control (RBAC) in Strapi can allow a bearer token with limited privileges to invoke admin-only actions, such as publishing content or modifying environment variables. If Strapi instances share containers or volumes across services, an API that leaks paths or internal host information through error messages can give an attacker clues to escape the container’s network namespace. For example, a path traversal via a crafted request to a media upload endpoint could expose files that should remain inside another container, revealing secrets or configuration that facilitate lateral movement.
Because Strapi’s API surface often includes user-generated content and dynamic routes, an attacker who chains a bearer token with insufficient authorization checks and container-aware endpoints may pivot from a standard API consumer to a host-level foothold. This is especially risky when tokens are long-lived or improperly scoped, and when the Strapi admin interface is inadvertently exposed through the same ingress as the public API.
middleBrick detects this category of issue under the BOLA/IDOR and BFLA/Privilege Escalation checks, correlating authentication signals with object-level authorization tests. It also flags unsafe exposure of environment details that can aid container escape attempts, providing prioritized findings with severity and remediation guidance.
Bearer Tokens-Specific Remediation in Strapi — concrete code fixes
To mitigate bearer token related authorization flaws in Strapi, enforce strict token scoping, validate ownership for every resource access, and avoid relying solely on token presence for authorization decisions. Below are concrete remediation patterns and code examples.
1. Scope-based token validation with ownership checks
Ensure each bearer token includes a scope or tenant identifier, and enforce that scope in your Strapi controllers or policies. For example, if you use JSON Web Tokens (JWTs), embed a tenantId claim and verify it matches the resource’s tenant before returning data.
// src/api/container/controllers/container.js
'use strict';
module.exports = {
async find(ctx) {
const { id } = ctx.params;
const tokenScopes = ctx.state.user?.scopes || [];
const container = await strapi.entityService.findOne('api::container.container', id);
if (!container) {
return ctx.notFound();
}
// Ensure the token scope matches the container’s tenant
if (!tokenScopes.includes(`tenant:${container.tenantId}`)) {
return ctx.unauthorized('Invalid scope for this container');
}
return container;
},
};
2. Enforce per-request ownership via a policy
Create a Strapi policy that checks that the authenticated entity (derived from the bearer token) owns or is authorized to access the requested resource. This policy can be reused across content types.
// src/policies/check-ownership.js
'use strict';
module.exports = async (ctx, next) => {
const user = ctx.state.user;
const { id } = ctx.params;
const resource = await strapi.db.query('api::container.container').findOne({
where: { id, ownerId: user.id },
});
if (!resource) {
return ctx.forbidden('You do not have access to this resource');
}
return next();
};
Register this policy in config/policies.json and apply it to relevant routes.
3. Use short-lived tokens and rotate secrets
Configure your authentication provider (e.g., Strapi’s admin JWT, OAuth2 integration) to issue short-lived access tokens and enforce refresh token rotation. This reduces the window for token misuse if a bearer token is leaked.
4. Avoid exposing internal paths and metadata
Ensure Strapi error messages do not leak filesystem paths or container internal details. Customize error handlers to return generic messages in production while logging specifics securely for investigation.
// src/middlewares/error-handler.js
'use strict';
module.exports = strapi => {
return async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = { error: 'An error occurred' };
strapi.log.error(err);
}
};
};
5. Validate and sanitize input to prevent traversal
Always validate and sanitize parameters that influence filesystem or container interactions. For media uploads, restrict file paths and avoid direct user input in destination directories.
// Example validation in a custom upload controller
const sanitizePath = (input) => {
return input.replace(/[^a-zA-Z0-9_/.-]/g, '');
};
module.exports = {
async upload(ctx) {
const safeDir = sanitizePath(ctx.request.body.directory || 'uploads');
// proceed with upload to safeDir
},
};