fix(backup): keep GUI backup paths under BACKUP_ROOT and limit the schedule

backup:configure could point automatic backups at any writable directory
(e.g. a statically served one) and set a cron of every second; an invalid
expression deleted the running job before failing, silently ending
backups.

- A path entered in the UI is resolved under BACKUP_ROOT (default
  ./backups) and refused if it leaves it; env values stay unrestricted.
- The schedule must be a five-field cron that runs at most hourly.
- reload() validates before replacing the running job.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-01 02:14:18 +02:00
1 parent bd592adf66
commit ebeb499a18
6 files changed
+148 -3

No files matched your search

@@ -3,6 +3,7 @@ import { PrismaService } from '../prisma/prisma.service';
import { AuditLogService } from '../audit/audit-log.service';
import { BackupSettings } from '../../generated/prisma';
import { UpdateBackupSettingsDto } from './dto/update-backup-settings.dto';
import { assertBackupCron, assertGuiBackupPath } from './backup-schedule-rules';
const SINGLETON_ID = 'singleton';
@@ -31,6 +32,12 @@ export class BackupSettingsService {
dto: UpdateBackupSettingsDto,
actingUserId: string,
): Promise<BackupSettings> {
if (dto.storagePath) {
assertGuiBackupPath(dto.storagePath);
}
if (dto.cronSchedule) {
assertBackupCron(dto.cronSchedule);
}
const before = await this.get();
const settings = await this.prisma.backupSettings.update({
where: { id: SINGLETON_ID },