Zip Slip in Adonisjs with Mutual Tls
Zip Slip in Adonisjs with Mutual Tls — how this specific combination creates or exposes the vulnerability
Zip Slip is a path traversal vulnerability that occurs when an archive entry is extracted without validating that file paths remain within the intended directory. In Adonisjs, this typically arises when processing uploaded archives (e.g., ZIP files) via third-party packages that do not enforce strict path canonicalization. When Mutual TLS (mTLS) is in use, the server authenticates clients via client certificates, which can create a false sense of security. mTLS ensures the identity of the requestor, but it does not sanitize or validate file paths inside uploaded archives. Consequently, an authenticated client with a valid certificate can supply a malicious archive containing paths like ../../../etc/passwd, and if the server extracts it without checking, the traversal succeeds. The combination therefore exposes a vulnerability that mTLS does not mitigate: authentication of the client does not equate to authorization for file operations. Adonisjs applications that accept archive uploads over mTLS channels remain susceptible if path traversal protections are not explicitly implemented at the extraction layer.
In practice, an attacker with a valid mTLS certificate could use social engineering or compromised credentials to obtain the certificate, then craft a request to an endpoint that processes archives. The server follows mTLS trust but proceeds to extract the archive using functions that do not resolve .. sequences or validate absolute paths. This maps to the OWASP API Top 10 category 'Broken Object Level Authorization' and can allow unauthorized file reads or writes on the host. The vulnerability is not in mTLS itself, but in the unchecked handling of archive contents after successful mutual authentication.
Mutual Tls-Specific Remediation in Adonisjs — concrete code fixes
To remediate Zip Slip in Adonisjs while using Mutual TLS, you must validate and sanitize archive paths independently of mTLS client identity. Below are concrete code examples for secure archive handling in an Adonisjs controller, assuming you use the archiver/adm-zip ecosystem for extraction.
Secure extraction with path canonicalization
import { join, resolve, normalize } from 'path'
import { ensureDir, pathExists } from 'fs-extra'
import { createUnzip } from 'zlib'
import { pipeline } from 'stream/promises'
import { createReadStream, createWriteStream } from 'fs'
export async function extractZipSafe(zipPath: string, destDir: string) {
const ensureDest = ensureDir(destDir)
if (!ensureDest) throw new Error('Cannot ensure destination directory')
// Resolve to absolute, canonical path to prevent symlink escapes
const resolvedDest = resolve(destDir)
// Validate that the destination itself is within an allowed base
const allowedBase = resolve(process.cwd(), 'uploads')
if (!resolvedDest.startsWith(allowedBase)) {
throw new Error('Destination outside allowed base')
}
// Stream extraction with path validation
const unzip = createUnzip()
unzip.on('entry', (entry) => {
const normalized = normalize(entry)
// Reject absolute paths and leading '..' segments
if (path.isAbsolute(normalized) || normalized.startsWith('..')) {
entry.autodrain()
throw new Error(`Invalid entry path: ${entry}`)
}
const target = resolve(join(resolvedDest, entry))
if (!target.startsWith(resolvedDest)) {
entry.autodrain()
throw new Error(`Path traversal detected: ${entry}`)
}
})
await pipeline(
createReadStream(zipPath),
unzip,
createWriteStream(resolvedDest)
)
}
Middleware to enforce mTLS + archive validation
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { validateClientCert } from 'src/Helpers/tls'
export default class SecureUploadMiddleware {
public async handle({ request, response, auth }: HttpContextContract, next: () => Promise) {
// Ensure mTLS client certificate is present and valid
const certInfo = validateClientCert(request)
if (!certInfo || !certInfo.verified) {
return response.unauthorized('Client certificate required')
}
// Optionally tie authorization to scopes claims in the cert
const scopes = certInfo.scopes || []
if (!scopes.includes('upload:zip')) {
return response.forbidden('Insufficient scope for ZIP upload')
}
await next()
}
}
Validation helper (example)
// src/Helpers/tls.ts
export function validateClientCert(ctx) {
const cert = ctx.request.header()['x-ssl-client-cert']
if (!cert) return null
// Perform verification against your CA store; this is a placeholder
const verified = verifyCertificateAgainstCA(cert)
return { verified, subject: extractSubject(cert), scopes: extractScopes(cert) }
}
These examples emphasize that mTLS secures the channel and client identity, but archive extraction must still enforce strict path validation. Use canonical resolution, prefix checks, and rejection of entries with .. or absolute paths. Combine this with mTLS scope checks to implement least privilege for upload operations.