different-ai/openwork · warning · GatewayHttpError

path_escape

path_escape

Error message

Path escapes web root

What it means

The Den gateway serves static files from a configured web root. resolveWithinRoot resolves requested path segments against the root, follows symlinks via realpath, and throws a 400 GatewayHttpError (code 'path_escape', message 'Path escapes web root') when the resolved candidate is not the root itself and does not remain inside it. This blocks path-traversal requests like /../../etc/passwd.

Source

Thrown at ee/apps/den-gateway/src/app.ts:291

function requestPathToRelativePath(pathname: string): string | null {
  try {
    const decoded = decodeURIComponent(pathname)
    return decoded.replace(/^\/+/, "") || "index.html"
  } catch {
    return null
  }
}

async function resolveWithinRoot(root: string, ...segments: string[]) {
  const resolvedRoot = await realpath(root)
  const candidate = resolve(resolvedRoot, ...segments)
  const resolvedCandidate = await realpath(candidate).catch(() => candidate)
  if (resolvedCandidate === resolvedRoot) {
    return candidate
  }
  if (!resolvedCandidate.startsWith(resolvedRoot + sep)) {
    throw new GatewayHttpError(400, "path_escape", "Path escapes web root")
  }
  return candidate
}

function contentType(extension: string) {
  if (extension === ".html") {
    return "text/html; charset=utf-8"
  }
  if (extension === ".js" || extension === ".mjs") {
    return "text/javascript; charset=utf-8"
  }
  if (extension === ".css") {
    return "text/css; charset=utf-8"
  }
  if (extension === ".json" || extension === ".map") {
    return "application/json; charset=utf-8"
  }
  if (extension === ".svg") {

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Remove or rewrite request paths containing '..' or encoded traversal segments before they reach the gateway
  2. Ensure DEN_GATEWAY_WEB_ROOT points at the directory actually containing the assets being requested
  3. Move or re-point any symlink inside the web root so targets live under the root
  4. Serve additional content by copying it into the web root or adding an explicit proxied route, not via symlinks

Example fix

// before
fetch('/static/../../etc/passwd') // path_escape
// after
const safe = requestedPath.replaceAll(/(\.\.\/)+/g, '')
fetch(`/static/${encodeURIComponent(safe)}`)
Defensive patterns

Strategy: try-catch

Validate before calling

function staysWithinRoot(root: string, segments: string[]) {
  const resolved = resolve(resolve(root), ...segments)
  return resolved === resolve(root) || resolved.startsWith(resolve(root) + sep)
}

Type guard

null

Try / catch

import { GatewayHttpError } from './http-error.js'
try {
  const file = await filePath(segments)
} catch (err) {
  if (err instanceof GatewayHttpError && err.code === 'path_escape') {
    return new Response('Not Found', { status: 404 }) // don't leak traversal attempts
  }
  throw err
}

Prevention

When it happens

Trigger: An HTTP request whose URL-encoded segments (../, %2e%2e/, absolute paths) resolve outside DEN_GATEWAY_WEB_ROOT, or a symlink inside the web root pointing to a file outside it.

Common situations: Attackers probing for traversal vulnerabilities; misconfigured reverse proxies forwarding raw '..' segments; a developer symlinking a shared asset directory that lives outside the web root; Windows/macOS case or symlink surprises after deployment.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/d539bfc3478e3d5a. Report an issue: GitHub.