fix(scheduler): reject 6-field cron in Scheduled Operations (#1435)

* fix(scheduler): reject 6-field cron in Scheduled Operations

Create and edit validation parsed cron with cron-parser, which accepts both
5- and 6-field expressions, while the form, presets, and docs all describe a
5-field cron. Because the scheduler ticks once per minute, a leading seconds
field can never improve precision, so a 6-field expression was silently
accepted but never honored on its stated schedule.

Add a field-count guard on both sides: the API rejects 6-field input at
create and edit with a clear message, and the form surfaces the same error
inline and disables save. Cron nicknames such as @daily still pass. Document
the five-field requirement in the cron reference.

* chore: merge main into scheduled cron validation

* fix: avoid logging policy bypass actor in debug output
This commit is contained in:
Anso
2026-06-24 23:00:56 -04:00
committed by GitHub
parent bc8c051962
commit db8bb70b7d
10 changed files with 715 additions and 472 deletions
+29
View File
@@ -0,0 +1,29 @@
import { describe, it, expect } from 'vitest';
import { getCronFieldError } from './scheduling';
describe('getCronFieldError', () => {
it('accepts a standard 5-field expression', () => {
expect(getCronFieldError('0 3 * * *')).toBeNull();
});
it('rejects a 6-field expression with a seconds field', () => {
expect(getCronFieldError('30 0 3 * * *')).toMatch(/5 fields/);
});
it('rejects expressions with extra fields beyond six', () => {
expect(getCronFieldError('0 0 3 * * * 2026')).toMatch(/5 fields/);
});
it('accepts cron nicknames such as @daily', () => {
expect(getCronFieldError('@daily')).toBeNull();
});
it('ignores empty or whitespace-only input', () => {
expect(getCronFieldError('')).toBeNull();
expect(getCronFieldError(' ')).toBeNull();
});
it('tolerates irregular spacing between five fields', () => {
expect(getCronFieldError(' 0 3 * * * ')).toBeNull();
});
});
+14
View File
@@ -8,6 +8,20 @@ export function getCronDescription(expression: string): string {
}
}
/**
* Reject cron expressions with a leading seconds field (6 or more fields). The
* scheduler is minute-granular, so the seconds field could never be honored.
* Nicknames like `@daily` (one token) pass. Returns an error message or null
* when the field count is acceptable.
*/
export function getCronFieldError(expression: string): string | null {
const trimmed = expression.trim();
if (trimmed && trimmed.split(/\s+/).length >= 6) {
return 'Use 5 fields (minute hour day month weekday). The seconds field is not supported.';
}
return null;
}
export function formatTimestamp(ts: number | null): string {
if (ts == null) return '-';
return new Date(ts).toLocaleString();