{"record":{"id":"0f6358324769b132","repo":"immich-app/immich","slug":"invalid-backup-file-format","errorCode":null,"errorMessage":"Invalid backup file format!","messagePattern":"Invalid backup file format!","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/database-backup.service.ts","lineNumber":370,"sourceCode":"    toDelete.push(...failedBackups);\n\n    for (const file of toDelete) {\n      await this.storageRepository.unlink(path.join(backupsFolder, file));\n    }\n\n    this.logger.debug(`Database Backup Cleanup Finished, deleted ${toDelete.length} backups`);\n  }\n\n  async restoreDatabaseBackup(\n    filename: string,\n    progressCb?: (action: 'backup' | 'restore' | 'migrations' | 'rollback', progress: number) => void,\n  ): Promise<void> {\n    this.logger.debug(`Database Restore Started`);\n\n    let isComplete = false;\n    try {\n      if (!isValidDatabaseBackupName(filename)) {\n        throw new Error('Invalid backup file format!');\n      }\n\n      const backupFilePath = path.join(StorageCore.getBaseFolder(StorageFolder.Backups), filename);\n      await this.storageRepository.stat(backupFilePath); // => check file exists\n\n      let isPgClusterDump = false;\n      const version = findDatabaseBackupVersion(filename);\n      if (version && satisfies(version, '<= 2.4')) {\n        isPgClusterDump = true;\n      }\n\n      const { bin, args, databaseUsername, databasePassword, databaseMajorVersion } =\n        await this.buildPostgresLaunchArguments('psql', {\n          singleTransaction: !isPgClusterDump,\n        });\n\n      progressCb?.('backup', 0.05);\n","sourceCodeStart":352,"sourceCodeEnd":388,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/database-backup.service.ts#L352-L388","documentation":"restoreDatabaseBackup refuses to restore a file whose name does not pass isValidDatabaseBackupName, throwing before any filesystem or database work. This guards against restoring arbitrary/attacker-controlled paths. The raw Error is caught in the method's try/catch and triggers a rollback of the restore.","triggerScenarios":"POSTing a restore request with a filename that does not match the Immich database backup naming pattern (e.g. uploaded file renamed, or a path like '../../dump.sql').","commonSituations":"Users renaming .sql.gz dumps before upload; selecting the wrong file from the backups folder; crafted filenames for path traversal.","solutions":["Restore only files that exist in the server's Backups storage folder with the original Immich-generated name (immich-dbBackup-*.sql.gz).","Rename the file back to its original backup name before requesting restore.","Verify the name with the same pattern locally: /^immich-dbBackup-.*\\.sql\\.gz$/ (or list backups via API and use returned names)."],"exampleFix":"// before\nawait api.restoreDatabaseBackup('my-dump.sql.gz');\n// after\nawait api.restoreDatabaseBackup('immich-dbBackup-1700000000000.sql.gz');","handlingStrategy":"validation","validationCode":"const BACKUP_NAME_RE = /^immich-dbBackup-.*\\.sql\\.gz$/;\nif (!BACKUP_NAME_RE.test(filename)) throw new Error('Not a valid Immich backup file name');","typeGuard":null,"tryCatchPattern":"try {\n  await api.restoreDatabaseBackup(filename);\n} catch (e) {\n  if (/Invalid backup file format/.test(e.message)) {\n    console.error('Use a server-side backup file with its original name:', filename);\n  } else throw e;\n}","preventionTips":["Keep the original immich-dbBackup-<ts>.sql.gz names when copying backups.","Restore only files listed by the server's backups endpoint.","Never pass paths or renamed dumps to the restore API."],"tags":["validation","backup","restore"],"backgroundTag":"invalid-argument-format","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}