{"record":{"id":"9c6690af7efe98fd","repo":"Budibase/budibase","slug":"invalid-zip-directory-depth-exceeds-max-pwa-zi","errorCode":null,"errorMessage":"Invalid zip - directory depth exceeds ${MAX_PWA_ZIP_DEPTH}","messagePattern":"Invalid zip - directory depth exceeds (.+?)","errorType":"http","errorClass":"BadRequestError","httpStatus":400,"severity":"error","filePath":"packages/server/src/api/controllers/static/index.ts","lineNumber":113,"sourceCode":"    fileName: string\n    uncompressedSize: number\n    externalFileAttributes: number\n  }) => {\n    // extract-zip skips these itself, so don't count them against the limits.\n    if (entry.fileName.startsWith(\"__MACOSX/\")) {\n      return\n    }\n\n    const fileType = (entry.externalFileAttributes >>> 16) & ZIP_FILE_TYPE_MASK\n    if (fileType === ZIP_SYMLINK_FILE_TYPE) {\n      throw new BadRequestError(`Invalid zip`)\n    }\n\n    const depth =\n      entry.fileName.split(\"/\").filter(Boolean).length -\n      (entry.fileName.endsWith(\"/\") ? 0 : 1)\n    if (depth > MAX_PWA_ZIP_DEPTH) {\n      throw new BadRequestError(\n        `Invalid zip - directory depth exceeds ${MAX_PWA_ZIP_DEPTH}`\n      )\n    }\n\n    // Directory entries carry no content, only enforce the depth limit on them.\n    if (entry.fileName.endsWith(\"/\")) {\n      return\n    }\n\n    fileCount++\n    if (fileCount > MAX_PWA_ZIP_FILE_COUNT) {\n      throw new BadRequestError(\n        `Invalid zip - too many files (max ${MAX_PWA_ZIP_FILE_COUNT})`\n      )\n    }\n\n    if (entry.uncompressedSize > MAX_PWA_ZIP_ENTRY_SIZE) {\n      throw new BadRequestError(","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/api/controllers/static/index.ts#L95-L131","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","solutions":["Flatten the zip so no path exceeds 10 directory levels before uploading","Remove unneeded wrapper folders from the archive root","Split assets across multiple uploads if the structure is inherently deep"],"exampleFix":"// before\nassets/js/chunks/vendor/lib/icons/16/launcher/icon.png  (11 levels)\n// after\nassets/icons/launcher/icon.png  (within 10 levels)","handlingStrategy":"validation","validationCode":"function isPwaZipDepthSafe(fileNames, maxDepth = 10) {\n  return fileNames.every(name => {\n    const depth = name.split('/').filter(Boolean).length - (name.endsWith('/') ? 0 : 1)\n    return depth <= maxDepth\n  })\n}","typeGuard":"const isZipEntry = (e: unknown): e is { fileName: string; uncompressedSize: number; externalFileAttributes: number } =>\n  typeof e === 'object' && e !== null && typeof (e as any).fileName === 'string' && typeof (e as any).uncompressedSize === 'number'","tryCatchPattern":"try {\n  await uploadPwaZip(zip)\n} catch (err) {\n  if (err instanceof BadRequestError && err.message.includes('directory depth')) {\n    // rebuild the archive with a flattened structure\n  } else throw err\n}","preventionTips":["Inspect zip entry paths before upload and flatten if deeper than 10 levels","Avoid zipping already-nested extracted output","Add a client-side pre-check of entry path depth with a zip library"],"tags":["validation","pwa","upload","zip"],"backgroundTag":"zip-validation-failed","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}