Container Escape in Adonisjs (Typescript)
Container Escape in Adonisjs with Typescript — how this specific combination creates or exposes the vulnerability
Adonisjs, a Node.js framework written in Typescript, provides a structured way to build server-side applications. When deployed in containerized environments (e.g., Docker), misconfigurations in file system permissions, privileged container execution, or insecure volume mounts can lead to container escape — where an attacker breaks out of the container to gain unauthorized access to the host system. In Adonisjs applications, this risk is amplified when:
- The application runs as root inside the container (common in development Dockerfiles that inherit from
node:latestwithout dropping privileges). - Volume mounts expose sensitive host paths (e.g.,
/var/run/docker.sock,/sys, or/proc) to the container, which Adonisjs routes may inadvertently expose via file upload or static file serving. - Typescript’s structural typing and compile-time safety do not prevent runtime misconfigurations; they only catch type errors, not insecure Dockerfile directives or volume mounts.
- An Adonisjs route that serves user-uploaded files without proper path validation (e.g., using
path.joinwithout sanitization) could allow an attacker to write a malicious script to a mounted host directory, which is then executed with host privileges.
For example, if a developer mounts the host’s /app directory into the container at /var/www and the Adonisjs application writes user avatars to /var/www/uploads, a path traversal flaw could let an attacker write a file to /app/../../etc/passwd — effectively escaping the container’s intended scope. middleBrick detects such risks by scanning for exposed internal endpoints (like /upload or /file) and testing for path traversal, insecure file permissions, and signs of privileged container execution (e.g., processes running as UID 0). It does not require agents or configuration — just the API URL — and reports findings with severity and remediation guidance.
Typescript-Specific Remediation in Adonisjs — concrete code fixes
To mitigate container escape risks in an Adonisjs application written in Typescript, focus on secure coding practices that prevent file system abuse, combined with secure container configuration. Below are specific, actionable fixes:
// Start: Secure file upload handler in Adonisjs (using @ioc:Adonis/Core/Drive)
import Drive from '@ioc:Adonis/Core/Drive'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { fileURLToPath } from 'url'
import { dirname, join, resolve } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
// Define a safe, isolated upload directory INSIDE the container (not mounted host paths)
const UPLOAD_DIR = resolve(__dirname, '..', 'tmp', 'uploads')
// Ensure the directory exists and is isolated
import { mkdir } from 'fs/promises'
await mkdir(UPLOAD_DIR, { recursive: true, mode: 0o700 })
export default class FileUploadController {
public async handle({ request, response }: HttpContextContract) {
const upload = request.file('avatar', {
size: '2mb',
extnames: ['jpg', 'png', 'gif']
})
if (!upload) {
return response.badRequest({ error: 'No file uploaded' })
}
// Generate a safe, random filename to prevent path manipulation
const fileName = `${crypto.randomUUID()}.${upload.extname}`
const filePath = join(UPLOAD_DIR, fileName)
// Explicitly validate the resolved path is within UPLOAD_DIR
const resolvedPath = resolve(filePath)
if (!resolvedPath.startsWith(UPLOAD_DIR)) {
return response.badRequest({ error: 'Invalid file path' })
}
await upload.move(UPLOAD_DIR, {
name: fileName,
overwrite: false
})
if (!upload.moved()) {
return response.badRequest({ error: upload.errors.messages })
}
return response.ok({ message: 'File uploaded securely', filePath: `/tmp/uploads/${fileName}` })
}
}
This code prevents path traversal by:
- Using a fixed, isolated upload directory (
tmp/uploads) inside the container — never relying on environment variables or user input for the base path. - Generating a cryptographically random filename to eliminate predictable paths.
- Validating that the resolved file path starts with the intended base directory using
path.resolveandstartsWith— a critical Typescript/runtime check. - Setting restrictive directory permissions (
0o700) to limit access even if the container is compromised.
Complement this with secure container practices:
- Run the Adonisjs process as a non-root user in Dockerfile:
USER node(assuming thenodeuser exists). - Avoid mounting host sockets or sensitive directories: never use
-v /var/run/docker.sock:/var/run/docker.sockor-v /sys/fs/cgroup:/sys/fs/cgroup:roin production. - Use
read_only: trueand drop unnecessary Linux capabilities (CAP_SYS_ADMIN, etc.) viasecurity_contextin Kubernetes or--cap-dropin Docker.
middleBrick helps validate these fixes by scanning the running API for exposed file endpoints, testing for path traversal and excessive privileges, and reporting if the container is running as root or has dangerous mounts — all without needing internal access or agents.
Frequently Asked Questions
Can middleBrick detect if my Adonisjs application is running as root inside a container?
/debug or misconfigured health checks) or by analyzing file system access patterns during testing. It does not require internal access — only the public API URL — and will flag if the application appears to be running with elevated privileges based on observable behaviors like successful access to protected system paths.