Skip to content

Commit 49c2088

Browse files
fix: max interval checks (diced#990)
* introduce max interval checks * Update validate.ts * Update validate.ts * Update validate.ts * Update validate.ts
1 parent 7860010 commit 49c2088

File tree

2 files changed

+43
-6
lines changed

2 files changed

+43
-6
lines changed

src/lib/config/validate.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import { log } from '../logger';
55
import { ParsedConfig } from './read';
66
import { PROP_TO_ENV } from './read/env';
77
import { checkOutput, COMPRESS_TYPES } from '../compress';
8+
import ms, { StringValue } from 'ms';
9+
10+
// Maximum safe timeout value for JavaScript timers (32-bit signed integer limit)
11+
// Approximately 24.8 days
12+
const MAX_SAFE_TIMEOUT_MS = 2147483647;
813

914
declare global {
1015
// eslint-disable-next-line @typescript-eslint/no-namespace
@@ -17,6 +22,23 @@ declare global {
1722
}
1823
}
1924

25+
// Helper function to validate interval strings
26+
function validateInterval(value: string): boolean {
27+
const intervalMs = ms(value as StringValue);
28+
// ms() returns undefined for invalid strings, not throw
29+
if (typeof intervalMs !== 'number') {
30+
return false;
31+
}
32+
return intervalMs <= MAX_SAFE_TIMEOUT_MS;
33+
}
34+
35+
// Reusable interval schema with validation
36+
const intervalSchema = (defaultValue: string) =>
37+
z
38+
.string()
39+
.default(defaultValue)
40+
.refine(validateInterval, 'Interval exceeds maximum safe timeout of ~24 days (2147483647ms)');
41+
2042
export const discordContent = z
2143
.object({
2244
webhookUrl: z.url().nullable().default(null),
@@ -104,12 +126,12 @@ export const schema = z.object({
104126
enabled: z.boolean().default(true),
105127
}),
106128
tasks: z.object({
107-
deleteInterval: z.string().default('30min'),
108-
clearInvitesInterval: z.string().default('30min'),
109-
maxViewsInterval: z.string().default('30min'),
110-
thumbnailsInterval: z.string().default('30min'),
111-
metricsInterval: z.string().default('30min'),
112-
cleanThumbnailsInterval: z.string().default('1d'),
129+
deleteInterval: intervalSchema('30min'),
130+
clearInvitesInterval: intervalSchema('30min'),
131+
maxViewsInterval: intervalSchema('30min'),
132+
thumbnailsInterval: intervalSchema('30min'),
133+
metricsInterval: intervalSchema('30min'),
134+
cleanThumbnailsInterval: intervalSchema('1d'),
113135
}),
114136
files: z.object({
115137
route: z.string().startsWith('/').min(1).trim().toLowerCase().default('/u'),

src/lib/tasks/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ export class Tasks {
8282
return;
8383
}
8484

85+
// Maximum safe timeout for JavaScript timers (32-bit signed integer limit)
86+
// Approximately 24.8 days or 2,147,483,647 milliseconds
87+
const MAX_SAFE_TIMEOUT_MS = 2147483647;
88+
89+
if (task.interval > MAX_SAFE_TIMEOUT_MS) {
90+
this.logger.error('interval exceeds maximum safe timeout', {
91+
id: task.id,
92+
interval: task.interval,
93+
maxSafeTimeout: MAX_SAFE_TIMEOUT_MS,
94+
message: 'Interval exceeds JavaScript timer limit (~24 days). Task will not be started.',
95+
});
96+
97+
return;
98+
}
99+
85100
task.started = true;
86101

87102
const timeout = setInterval(task.func.bind(task), task.interval);

0 commit comments

Comments
 (0)