HIGH symlink attackadonisjsbasic auth

Symlink Attack in Adonisjs with Basic Auth

Symlink Attack in Adonisjs with Basic Auth — how this specific combination creates or exposes the vulnerability

A symlink attack in AdonisJS occurs when an attacker tricks the application into reading or writing files outside the intended directory by leveraging insecure path resolution. When Basic Authentication is used but not strictly coupled with file-system operations, developers may assume authentication is sufficient to protect sensitive routes. This can lead to dangerously permissive route or controller logic that exposes file-handling endpoints without adequate path validation.

Consider an AdonisJS route that serves user-uploaded files using a raw path concatenation, protected only by a Basic Auth guard:

import Route from '@ioc:Adonis/Core/Route'
import fs from 'fs'
import path from 'path'

Route.get('/files/:filename', async ({ params, auth }) => {
  const user = auth.getUserOrFail()
  // Basic Auth protects this route, but path traversal is unchecked
  const base = `uploads/${user.username}/`
  const filePath = path.join(base, params.filename)
  return fs.readFileSync(filePath)
})

Although the route requires Basic Auth, there is no validation that params.filename is confined to the user’s directory. An attacker can supply a filename like ../../../etc/passwd and, because the path is resolved relative to the process root, read arbitrary files. The presence of Basic Auth gives a false sense of security, while the missing path normalization and directory confinement enable the symlink attack.

If the application also allows file uploads and uses predictable names or symlinks (e.g., storing user content as symlinks to temporary storage), an authenticated attacker may replace a trusted symlink with a link to a sensitive system file. On the next read, the application may unintentionally expose or modify system resources. This becomes more impactful when combined with misconfigured static file serving or when logs are written to user-controlled paths, enabling log injection or overwrite attacks.

In an API security scan with middleBrick, such misconfigurations are surfaced across multiple checks, including Input Validation and Property Authorization. The scanner identifies endpoints that accept file paths or names without strict allowlisting, resolves any referenced OpenAPI/Swagger specs with full $ref resolution, and correlates runtime behavior with spec definitions to highlight deviations. Even though middleBrick does not fix or block, it provides prioritized findings with severity and remediation guidance to help developers tighten path handling and authentication coupling.

Basic Auth-Specific Remediation in Adonisjs — concrete code fixes

To mitigate symlink attacks in AdonisJS when using Basic Auth, enforce strict path confinement and avoid direct use of user-supplied filenames. Combine route protection with filesystem safeguards such as canonical path resolution, allowlisted extensions, and chroot-like base directories.

Use AdonisJS middleware to load the authenticated user and validate access to the requested resource before any filesystem operation:

import Route from '@ioc:Adonis/Core/Route'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import fs from 'fs'
import path from 'path'

Route.get('/files/:filename', async (ctx: HttpContextContract) => {
  const user = ctx.auth.getUserOrFail()
  const requested = ctx.params.filename

  // Canonicalize and confine to user directory
  const safeName = path.normalize(requested).replace(/^(\.\.[\/\\])+/, '')
  const base = path.resolve(`uploads/${user.username}/`)
  const filePath = path.resolve(base, safeName)

  // Ensure the resolved path stays within base
  if (!filePath.startsWith(base)) {
    throw new Error('Invalid path')
  }

  if (!fs.existsSync(filePath)) {
    return ctx.response.notFound()
  }

  return fs.readFileSync(filePath)
})

Additionally, prefer a database-driven mapping between user-owned files and storage keys instead of directly exposing filenames. Generate random filenames on upload and store metadata in the database, then look up by ID rather than trusting path segments:

import Route from '@ioc:Adonis/Core/Route'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import fs from 'fs'
import path from 'path'
import File from 'App/Models/File'

Route.get('/files/:id', async (ctx: HttpContextContract) => {
  const user = ctx.auth.getUserOrFail()
  const file = await File.findByOrFail('id', ctx.params.id)

  if (file.userId !== user.id) {
    return ctx.response.forbidden()
  }

  const filePath = path.resolve(`uploads/${file.storageKey}`)
  if (!fs.existsSync(filePath)) {
    return ctx.response.notFound()
  }

  return fs.readFileSync(filePath)
})

These patterns reduce reliance on raw user input for filesystem paths and minimize the attack surface for symlink or traversal techniques. When combined with middleBrick’s continuous monitoring in the Pro plan, you can detect regressions in path validation across changes and enforce stricter controls as part of your CI/CD pipeline using the GitHub Action to fail builds if risk thresholds are exceeded.

Frequently Asked Questions

Does Basic Auth alone prevent symlink attacks in AdonisJS file endpoints?
No. Basic Auth protects routes but does not prevent path traversal or symlink manipulation if file paths are constructed from unchecked user input. You must canonicalize paths and confine files to a strict base directory.
How can I test that my AdonisJS endpoints are resilient to symlink attacks?
Use tools that include Input Validation and Property Authorization checks, such as middleBrick, to identify endpoints that accept file or path parameters without allowlisting. Review resolved paths and ensure no user-controlled segments escape the intended directory.