Budibase/budibase · error · HTTPError

Project package contains unsafe paths.

Error message

Project package contains unsafe paths.

What it means

While recursively reading the extracted package, every path is relativized to the archive root and checked with isSafeArchivePath. If a path escapes the root (e.g. .. traversal, absolute paths) the import aborts with this 400 HTTPError. This prevents zip/tar-slip attacks where a malicious archive writes files outside the target directory.

Source

Thrown at packages/server/src/sdk/workspace/projects/backups/imports.ts:187

    !path.startsWith("\\") &&
    !/^[A-Za-z]:/.test(path) &&
    segments.every(segment => segment !== ".." && segment !== ".")
  )
}

const readDirectoryRecursively = async (
  dirPath: string,
  rootPath = dirPath,
  totals = { files: 0, bytes: 0 }
): Promise<string[]> => {
  const entries = await fsp.readdir(dirPath, { withFileTypes: true })
  const files: string[] = []

  for (const entry of entries) {
    const fullPath = join(dirPath, entry.name)
    const relPath = relative(rootPath, fullPath)
    if (!isSafeArchivePath(relPath)) {
      throw new HTTPError("Project package contains unsafe paths.", 400)
    }
    if (relPath.split(/[\\/]/).length > MAX_PATH_SEGMENTS) {
      throw new HTTPError(
        "Project package contains paths that are too deep.",
        400
      )
    }
    if (entry.isSymbolicLink()) {
      throw new HTTPError("Project package contains unsupported links.", 400)
    }
    if (entry.isDirectory()) {
      files.push(
        ...(await readDirectoryRecursively(fullPath, rootPath, totals))
      )
    } else {
      const stats = await fsp.stat(fullPath)
      totals.files += 1
      totals.bytes += stats.size

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Inspect the archive listing (tar -tf) for entries with .. or absolute paths and remove them
  2. Regenerate the package with Budibase's own export flow
  3. Only import packages from trusted sources
  4. Sanitize entry names to be relative and contained within the archive root

Example fix

// before
// archive entry: ../../etc/passwd
// after
// archive entry: files/etc-passwd  (relative, inside package root)
Defensive patterns

Strategy: validation

Validate before calling

import { isSafeArchivePath } from "@budibase/backend-core" // same guard the importer uses
for (const entry of entries) {
  if (!isSafeArchivePath(relative(root, entry))) throw new Error(`Unsafe archive entry: ${entry}`)
}

Type guard

function isContainedPath(relPath: string): boolean {
  return !!relPath && !relPath.startsWith("..") && !isAbsolute(relPath)
}

Try / catch

try {
  await importProject(packagePath)
} catch (e) {
  if (e instanceof HTTPError && e.message.includes("unsafe paths")) {
    // reject/inspect the untrusted package — do not retry as-is
  } else throw e
}

Prevention

When it happens

Trigger: Importing a package whose archive entries contain path traversal (../), absolute paths, or names that relativize outside rootPath — typically a deliberately malicious or improperly generated archive.

Common situations: Third-party or tampered .tar.gz packages; archives built on Windows with odd separators/absolute entries; custom scripts that add entries with leading '/' or '..' components.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/e8c337af6db17aa3. Report an issue: GitHub.