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:
1 parent
bd592adf66
commit
ebeb499a18
6 files changed
+148
-3
No files matched your search
@@ -0,0 +1,50 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import * as path from 'node:path';
|
||||
import {
|
||||
assertBackupCron,
|
||||
assertGuiBackupPath,
|
||||
resolveGuiBackupPath,
|
||||
} from './backup-schedule-rules';
|
||||
|
||||
describe('backup-schedule-rules', () => {
|
||||
const originalRoot = process.env.BACKUP_ROOT;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.BACKUP_ROOT = path.resolve('/data/backups');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.env.BACKUP_ROOT = originalRoot;
|
||||
});
|
||||
|
||||
it('keeps a GUI path under the backup root', () => {
|
||||
expect(resolveGuiBackupPath('nightly')).toBe(
|
||||
path.resolve('/data/backups/nightly'),
|
||||
);
|
||||
expect(() => assertGuiBackupPath('nightly')).not.toThrow();
|
||||
});
|
||||
|
||||
it('refuses a GUI path that leaves the backup root', () => {
|
||||
expect(() => assertGuiBackupPath('../../app/public')).toThrow(
|
||||
BadRequestException,
|
||||
);
|
||||
expect(() => assertGuiBackupPath('/etc')).toThrow(BadRequestException);
|
||||
expect(() => assertGuiBackupPath('/data/backups-evil')).toThrow(
|
||||
BadRequestException,
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts a daily schedule', () => {
|
||||
expect(() => assertBackupCron('0 3 * * *')).not.toThrow();
|
||||
});
|
||||
|
||||
it('refuses a schedule that runs more than hourly or has seconds', () => {
|
||||
expect(() => assertBackupCron('* * * * *')).toThrow(BadRequestException);
|
||||
expect(() => assertBackupCron('*/10 * * * *')).toThrow(BadRequestException);
|
||||
expect(() => assertBackupCron('* * * * * *')).toThrow(BadRequestException);
|
||||
});
|
||||
|
||||
it('refuses an invalid expression', () => {
|
||||
expect(() => assertBackupCron('every day')).toThrow(BadRequestException);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user