Sandbox Escape in Adonisjs with Api Keys
Sandbox Escape in Adonisjs with Api Keys — how this specific combination creates or exposes the vulnerability
A sandbox escape in AdonisJS when API keys are used occurs when an API key–based authorization boundary fails to isolate tenant or user contexts, allowing an authenticated request to bypass intended resource constraints. This typically happens when route parameter validation is incomplete, dynamic route segments are concatenated without sanitization, and API keys are mapped to roles or scopes that inadvertently grant broader filesystem or process access.
Consider an AdonisJS route that uses an API key to identify a tenant but relies only on a route parameter for scoped data access:
Route.get('/tenants/:tenantId/files/:fileId', async (ctx) => {
const { tenantId, fileId } = ctx.params;
const apiKey = ctx.request.header('X-API-KEY');
const tenant = await Tenant.findBy('api_key', apiKey);
if (!tenant || tenant.id !== tenantId) {
return ctx.response.unauthorized('Invalid tenant');
}
const file = await File.findOrFail(fileId);
return ctx.response.send(file);
});
The vulnerability emerges when fileId is user controlled without verifying that the file belongs to the tenant identified by the API key. An attacker who knows or guesses another tenant’s file ID can read files across tenant boundaries (BOLA/IDOR). If the file path is used directly in filesystem operations (e.g., fs.readFileSync(file.path)) without ensuring the resolved path is within the tenant’s allowed directory, a sandbox escape may occur by traversing outside the tenant’s directory (e.g., ../../../etc/passwd).
AdonisJS route templates like the following can inadvertently expose path traversal if fileId is concatenated into a filesystem path without normalization and strict prefix checks:
const fileBase = `storage/tenants/${tenantId}`;
const filePath = path.join(fileBase, fileId);
const content = fs.readFileSync(filePath, 'utf8');
If fileId contains sequences such as ../../, the resolved filePath can escape the intended tenant directory. Even when API keys are used to authenticate and identify tenants, improper authorization between the tenant and the specific resource allows an authenticated request to reach sensitive host resources, effectively breaking the runtime sandbox.
In an API security scan, this pattern maps to the BOLA/IDOR and Property Authorization checks. The API key ensures the request is associated with a valid tenant, but it does not guarantee the request is authorized for the specific file. The scan highlights missing ownership checks and unsafe path construction, which can lead to information disclosure or, in broader contexts, sandbox escape when filesystem operations are involved.
Api Keys-Specific Remediation in Adonisjs — concrete code fixes
To mitigate sandbox escape risks tied to API keys in AdonisJS, enforce strict tenant ownership checks and canonicalize paths before filesystem access. Use a two-step verification: first validate the API key to identify the tenant, then ensure the resource being accessed is explicitly owned by that tenant.
1) Validate tenant ownership at the resource level, not just at the route parameter level:
Route.get('/tenants/:tenantId/files/:fileId', async (ctx) => {
const { tenantId, fileId } = ctx.params;
const apiKey = ctx.request.header('X-API-KEY');
const tenant = await Tenant.findBy('api_key', apiKey);
if (!tenant || tenant.id !== tenantId) {
return ctx.response.unauthorized('Invalid tenant');
}
// Enforce ownership explicitly
const file = await File.query()
.where('id', fileId)
.where('tenant_id', tenant.id)
.firstOrFail();
return ctx.response.send(file);
});
2) Sanitize and restrict file paths using a controlled base directory and path resolution:
const fileBase = `storage/tenants/${tenantId}`;
// Ensure fileId is a safe relative name
if (fileId.includes('..') || path.isAbsolute(fileId)) {
throw new Error('Invalid file identifier');
}
const resolvedBase = path.resolve(fileBase);
const filePath = path.resolve(fileBase, fileId);
// Ensure the resolved path stays within the tenant base
if (!filePath.startsWith(resolvedBase)) {
throw new Error('Path traversal attempt detected');
}
const content = fs.readFileSync(filePath, 'utf8');
3) Prefer AdonisJS’s built-in validation to constrain identifiers and use a service layer to encapsulate authorization logic:
import { schema } from '@ioc:Adonis/Core/Validator';
const fileSchema = schema.create({
fileId: schema.string({ trim: true, escape: true })
});
const validated = await ctx.validate({ schema: fileSchema });
const file = await File.query()
.where('id', validated.fileId)
.where('tenant_id', tenant.id)
.firstOrFail();
These steps ensure that API key–based authentication is complemented by robust authorization and safe path handling, reducing the likelihood of a sandbox escape via directory traversal or IDOR.