Skip to content

Commit b3afb29

Browse files
committed
Fail fast on malformed numeric stackflow-node env values
1 parent ef0b2e5 commit b3afb29

3 files changed

Lines changed: 43 additions & 6 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,9 @@ cannot be disabled accidentally via negative values.
284284
Boolean env vars accept `true/false`, `1/0`, `yes/no`, and `on/off`
285285
(case-insensitive); invalid boolean text now fails fast to prevent silent
286286
misconfiguration.
287+
Integer env vars must be plain integer text (for example `10000`, not `10s`
288+
or `12.5`); malformed numeric values fail fast instead of being silently
289+
coerced.
287290
Observer ingress controls:
288291

289292
- `STACKFLOW_NODE_OBSERVER_LOCALHOST_ONLY` defaults to `true` and restricts

server/src/config.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,37 @@ const DEFAULT_DB_FILE = path.resolve(
2626
'server/data/stackflow-node-state.db',
2727
);
2828

29-
function parseInteger(value: unknown, fallback: number): number {
29+
function parseInteger(value: unknown, fallback: number, key: string): number {
3030
if (value === undefined || value === null || value === '') {
3131
return fallback;
3232
}
3333

34-
const parsed = Number.parseInt(String(value), 10);
35-
return Number.isFinite(parsed) ? parsed : fallback;
34+
const normalized = String(value).trim();
35+
if (!/^-?\d+$/.test(normalized)) {
36+
throw new Error(`${key} must be an integer`);
37+
}
38+
39+
const parsed = Number.parseInt(normalized, 10);
40+
if (!Number.isSafeInteger(parsed)) {
41+
throw new Error(`${key} must be a safe integer`);
42+
}
43+
44+
return parsed;
3645
}
3746

3847
function parsePort(value: unknown): number {
39-
const parsed = parseInteger(value, DEFAULT_PORT);
48+
const parsed = parseInteger(value, DEFAULT_PORT, 'STACKFLOW_NODE_PORT');
4049
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65_535) {
4150
throw new Error('STACKFLOW_NODE_PORT must be an integer between 1 and 65535');
4251
}
4352
return parsed;
4453
}
4554

4655
function parseMaxRecentEvents(value: unknown): number {
47-
return Math.max(1, parseInteger(value, DEFAULT_MAX_RECENT_EVENTS));
56+
return Math.max(
57+
1,
58+
parseInteger(value, DEFAULT_MAX_RECENT_EVENTS, 'STACKFLOW_NODE_MAX_RECENT_EVENTS'),
59+
);
4860
}
4961

5062
function parseCsv(value: unknown): string[] {
@@ -218,6 +230,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): StackflowNodeC
218230
parseInteger(
219231
env.STACKFLOW_NODE_PEER_WRITE_RATE_LIMIT_PER_MINUTE,
220232
DEFAULT_PEER_WRITE_RATE_LIMIT_PER_MINUTE,
233+
'STACKFLOW_NODE_PEER_WRITE_RATE_LIMIT_PER_MINUTE',
221234
),
222235
),
223236
trustProxy: parseBoolean(
@@ -249,13 +262,18 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): StackflowNodeC
249262
),
250263
forwardingMinFee: Math.max(
251264
0,
252-
parseInteger(env.STACKFLOW_NODE_FORWARDING_MIN_FEE, 0),
265+
parseInteger(
266+
env.STACKFLOW_NODE_FORWARDING_MIN_FEE,
267+
0,
268+
'STACKFLOW_NODE_FORWARDING_MIN_FEE',
269+
),
253270
).toString(10),
254271
forwardingTimeoutMs: Math.max(
255272
1_000,
256273
parseInteger(
257274
env.STACKFLOW_NODE_FORWARDING_TIMEOUT_MS,
258275
DEFAULT_FORWARDING_TIMEOUT_MS,
276+
'STACKFLOW_NODE_FORWARDING_TIMEOUT_MS',
259277
),
260278
),
261279
forwardingAllowPrivateDestinations: parseBoolean(
@@ -271,13 +289,15 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): StackflowNodeC
271289
parseInteger(
272290
env.STACKFLOW_NODE_FORWARDING_REVEAL_RETRY_INTERVAL_MS,
273291
DEFAULT_FORWARDING_REVEAL_RETRY_INTERVAL_MS,
292+
'STACKFLOW_NODE_FORWARDING_REVEAL_RETRY_INTERVAL_MS',
274293
),
275294
),
276295
forwardingRevealRetryMaxAttempts: Math.max(
277296
1,
278297
parseInteger(
279298
env.STACKFLOW_NODE_FORWARDING_REVEAL_RETRY_MAX_ATTEMPTS,
280299
DEFAULT_FORWARDING_REVEAL_RETRY_MAX_ATTEMPTS,
300+
'STACKFLOW_NODE_FORWARDING_REVEAL_RETRY_MAX_ATTEMPTS',
281301
),
282302
),
283303
};

tests/stackflow-node-config.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,20 @@ describe('stackflow-node config parsing', () => {
8383
).toThrow(/STACKFLOW_NODE_FORWARDING_ENABLED must be a boolean/);
8484
});
8585

86+
it('fails fast when integer env vars contain non-integer text', () => {
87+
expect(() =>
88+
loadConfig({
89+
STACKFLOW_NODE_FORWARDING_TIMEOUT_MS: '30s',
90+
}),
91+
).toThrow(/STACKFLOW_NODE_FORWARDING_TIMEOUT_MS must be an integer/);
92+
93+
expect(() =>
94+
loadConfig({
95+
STACKFLOW_NODE_PEER_WRITE_RATE_LIMIT_PER_MINUTE: '12.5',
96+
}),
97+
).toThrow(/STACKFLOW_NODE_PEER_WRITE_RATE_LIMIT_PER_MINUTE must be an integer/);
98+
});
99+
86100
it('rejects stackflow-node ports outside the TCP range', () => {
87101
expect(() =>
88102
loadConfig({

0 commit comments

Comments
 (0)