Budibase/budibase · error · BadRequestError

Invalid zip - directory depth exceeds ${MAX_PWA_ZIP_DEPTH}

Error message

Invalid zip - directory depth exceeds ${MAX_PWA_ZIP_DEPTH}

What it means

Thrown by validatePWAZipEntries when a PWA zip archive contains a file or directory whose path nesting is deeper than MAX_PWA_ZIP_DEPTH (10). The depth is computed by splitting entry.fileName on '/' and adjusting for trailing directory entries. This guard limits recursive extraction depth and prevents zip-bomb style deep nesting attacks.

Source

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

    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) {
      throw new BadRequestError(
        `Invalid zip - too many files (max ${MAX_PWA_ZIP_FILE_COUNT})`
      )
    }

    if (entry.uncompressedSize > MAX_PWA_ZIP_ENTRY_SIZE) {
      throw new BadRequestError(

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Flatten the zip so no path exceeds 10 directory levels before uploading
  2. Remove unneeded wrapper folders from the archive root
  3. Split assets across multiple uploads if the structure is inherently deep

Example fix

// before
assets/js/chunks/vendor/lib/icons/16/launcher/icon.png  (11 levels)
// after
assets/icons/launcher/icon.png  (within 10 levels)
Defensive patterns

Strategy: validation

Validate before calling

function isPwaZipDepthSafe(fileNames, maxDepth = 10) {
  return fileNames.every(name => {
    const depth = name.split('/').filter(Boolean).length - (name.endsWith('/') ? 0 : 1)
    return depth <= maxDepth
  })
}

Type guard

const isZipEntry = (e: unknown): e is { fileName: string; uncompressedSize: number; externalFileAttributes: number } =>
  typeof e === 'object' && e !== null && typeof (e as any).fileName === 'string' && typeof (e as any).uncompressedSize === 'number'

Try / catch

try {
  await uploadPwaZip(zip)
} catch (err) {
  if (err instanceof BadRequestError && err.message.includes('directory depth')) {
    // rebuild the archive with a flattened structure
  } else throw err
}

Prevention

When it happens

Trigger: Uploading a PWA zip (via processPWAZip) where any entry's path has more than 10 directory levels, e.g. 'a/b/c/d/e/f/g/h/i/j/k/icon.png'.

Common situations: Packaging a PWA from a build tool that outputs deeply nested hashed asset folders (e.g. assets/js/chunk/vendor/... chains), or re-uploading a zip that extracted inside a prior folder structure.

Related errors


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