Budibase/budibase · error · BadRequestError

Invalid zip

Error message

Invalid zip

What it means

When importing a PWA zip, each entry is scanned for security hazards. The entry's external file attributes are inspected for the unix file-type bits; if the entry is a symbolic link (ZIP_SYMLINK_FILE_TYPE) the zip is rejected as a BadRequestError to prevent symlink-based path traversal / file-overwrite attacks during extraction.

Source

Thrown at packages/server/src/api/controllers/static/index.ts:106

const ZIP_SYMLINK_FILE_TYPE = 0o120000

const validatePWAZipEntries = () => {
  let fileCount = 0
  let totalUncompressedSize = 0

  return (entry: {
    fileName: string
    uncompressedSize: number
    externalFileAttributes: number
  }) => {
    // extract-zip skips these itself, so don't count them against the limits.
    if (entry.fileName.startsWith("__MACOSX/")) {
      return
    }

    const fileType = (entry.externalFileAttributes >>> 16) & ZIP_FILE_TYPE_MASK
    if (fileType === ZIP_SYMLINK_FILE_TYPE) {
      throw new BadRequestError(`Invalid zip`)
    }

    const depth =
      entry.fileName.split("/").filter(Boolean).length -
      (entry.fileName.endsWith("/") ? 0 : 1)
    if (depth > MAX_PWA_ZIP_DEPTH) {
      throw new BadRequestError(
        `Invalid zip - directory depth exceeds ${MAX_PWA_ZIP_DEPTH}`
      )
    }

    // Directory entries carry no content, only enforce the depth limit on them.
    if (entry.fileName.endsWith("/")) {
      return
    }

    fileCount++
    if (fileCount > MAX_PWA_ZIP_FILE_COUNT) {

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Rebuild the zip resolving symlinks to real files (e.g. `zip -r pwa.zip . -y` without -y, or follow links during packaging).
  2. Exclude symlink entries when packaging: filter them out of the archive or delete them from the build output.
  3. Use a bundler/archiver configuration that dereferences symlinks (e.g. archiver with `follow: true`).
  4. Remove macOS junk like __MACOSX/ and .DS_Store and re-export the archive cleanly.

Example fix

// before (preserves symlinks)
execSync("zip -ry pwa.zip dist/")
// after (dereferences symlinks)
execSync("zip -r pwa.zip dist/")
Defensive patterns

Strategy: validation

Validate before calling

// client-side pre-check: reject zips that contain symlinks before upload
// e.g. with yauzl on the producing side, or shell: `unzip -Z1 pwa.zip` combined with
// `zipinfo pwa.zip | grep '^l'` — fail fast if any symlink entries exist
if (execSync("zipinfo -1 pwa.zip | awk -F' -> ' 'NR && $2').toString().trim()) {
  throw new Error("Zip contains symlinks; rebuild without them")
}

Type guard

function isRegularFileEntry(entry: { externalFileAttributes: number }): boolean {
  const fileType = (entry.externalFileAttributes >>> 16) & 0o170000
  return fileType !== 0o120000 // S_IFLNK
}

Try / catch

try {
  await uploadPwaZip(zipPath)
} catch (err) {
  if (err instanceof Error && err.message === "Invalid zip") {
    console.error("Zip rejected: contains symlink entries — rebuild with dereferenced symlinks")
  }
  throw err
}

Prevention

When it happens

Trigger: Uploading a PWA zip that contains symlink entries — commonly zips built on macOS or Linux with tools that preserve symlinks (tar->zip conversions, node_modules zips, zips containing __MACOSX metadata); validatePWAZipEntries throws at packages/server/src/api/controllers/static/index.ts:106.

Common situations: Building a PWA bundle on macOS where the archiver follows/preserves symlinks; packaging a build output directory that includes symlinked assets; zipping with `zip -y` or equivalent that stores symlinks rather than their targets.

Related errors


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