Skip to content

Commit 592d886

Browse files
committed
Fail fast on invalid stackflow-node boolean env config
1 parent 7d79d30 commit 592d886

3 files changed

Lines changed: 37 additions & 4 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,9 @@ exposure. Use a public bind only with hardened ingress controls.
281281
invalid values.
282282
`STACKFLOW_NODE_MAX_RECENT_EVENTS` is clamped to at least `1` so event pruning
283283
cannot be disabled accidentally via negative values.
284+
Boolean env vars accept `true/false`, `1/0`, `yes/no`, and `on/off`
285+
(case-insensitive); invalid boolean text now fails fast to prevent silent
286+
misconfiguration.
284287
Observer ingress controls:
285288

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

server/src/config.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ function parsePrincipalCsv(value: unknown): string[] {
7272
return [...new Set(principals)];
7373
}
7474

75-
function parseBoolean(value: unknown, fallback: boolean): boolean {
75+
function parseBoolean(value: unknown, fallback: boolean, key: string): boolean {
7676
if (value === undefined || value === null || value === '') {
7777
return fallback;
7878
}
@@ -85,7 +85,7 @@ function parseBoolean(value: unknown, fallback: boolean): boolean {
8585
return false;
8686
}
8787

88-
return fallback;
88+
throw new Error(`${key} must be a boolean (true/false, 1/0, yes/no, on/off)`);
8989
}
9090

9191
function normalizeBaseUrl(input: string): string {
@@ -172,7 +172,11 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): StackflowNodeC
172172
port: parsePort(env.STACKFLOW_NODE_PORT),
173173
dbFile,
174174
maxRecentEvents: parseMaxRecentEvents(env.STACKFLOW_NODE_MAX_RECENT_EVENTS),
175-
logRawEvents: parseBoolean(env.STACKFLOW_NODE_LOG_RAW_EVENTS, false),
175+
logRawEvents: parseBoolean(
176+
env.STACKFLOW_NODE_LOG_RAW_EVENTS,
177+
false,
178+
'STACKFLOW_NODE_LOG_RAW_EVENTS',
179+
),
176180
watchedContracts: parseCsv(env.STACKFLOW_CONTRACTS),
177181
watchedPrincipals: parsePrincipalCsv(env.STACKFLOW_NODE_PRINCIPALS),
178182
stacksNetwork: parseNetwork(env.STACKS_NETWORK),
@@ -207,6 +211,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): StackflowNodeC
207211
disputeOnlyBeneficial: parseBoolean(
208212
env.STACKFLOW_NODE_DISPUTE_ONLY_BENEFICIAL,
209213
false,
214+
'STACKFLOW_NODE_DISPUTE_ONLY_BENEFICIAL',
210215
),
211216
peerWriteRateLimitPerMinute: Math.max(
212217
0,
@@ -218,22 +223,30 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): StackflowNodeC
218223
trustProxy: parseBoolean(
219224
env.STACKFLOW_NODE_TRUST_PROXY,
220225
DEFAULT_TRUST_PROXY,
226+
'STACKFLOW_NODE_TRUST_PROXY',
221227
),
222228
observerLocalhostOnly: parseBoolean(
223229
env.STACKFLOW_NODE_OBSERVER_LOCALHOST_ONLY,
224230
DEFAULT_OBSERVER_LOCALHOST_ONLY,
231+
'STACKFLOW_NODE_OBSERVER_LOCALHOST_ONLY',
225232
),
226233
observerAllowedIps: parseCsv(env.STACKFLOW_NODE_OBSERVER_ALLOWED_IPS),
227234
adminReadToken: env.STACKFLOW_NODE_ADMIN_READ_TOKEN?.trim() || null,
228235
adminReadLocalhostOnly: parseBoolean(
229236
env.STACKFLOW_NODE_ADMIN_READ_LOCALHOST_ONLY,
230237
DEFAULT_ADMIN_READ_LOCALHOST_ONLY,
238+
'STACKFLOW_NODE_ADMIN_READ_LOCALHOST_ONLY',
231239
),
232240
redactSensitiveReadData: parseBoolean(
233241
env.STACKFLOW_NODE_REDACT_SENSITIVE_READ_DATA,
234242
DEFAULT_REDACT_SENSITIVE_READ_DATA,
243+
'STACKFLOW_NODE_REDACT_SENSITIVE_READ_DATA',
244+
),
245+
forwardingEnabled: parseBoolean(
246+
env.STACKFLOW_NODE_FORWARDING_ENABLED,
247+
false,
248+
'STACKFLOW_NODE_FORWARDING_ENABLED',
235249
),
236-
forwardingEnabled: parseBoolean(env.STACKFLOW_NODE_FORWARDING_ENABLED, false),
237250
forwardingMinFee: Math.max(
238251
0,
239252
parseInteger(env.STACKFLOW_NODE_FORWARDING_MIN_FEE, 0),
@@ -248,6 +261,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): StackflowNodeC
248261
forwardingAllowPrivateDestinations: parseBoolean(
249262
env.STACKFLOW_NODE_FORWARDING_ALLOW_PRIVATE_DESTINATIONS,
250263
false,
264+
'STACKFLOW_NODE_FORWARDING_ALLOW_PRIVATE_DESTINATIONS',
251265
),
252266
forwardingAllowedBaseUrls: parseCsv(
253267
env.STACKFLOW_NODE_FORWARDING_ALLOWED_BASE_URLS,

tests/stackflow-node-config.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,22 @@ describe('stackflow-node config parsing', () => {
6767
expect(config.forwardingRevealRetryMaxAttempts).toBe(1);
6868
});
6969

70+
it('parses boolean aliases and rejects invalid boolean text', () => {
71+
const config = loadConfig({
72+
STACKFLOW_NODE_FORWARDING_ENABLED: 'YeS',
73+
STACKFLOW_NODE_TRUST_PROXY: '0',
74+
});
75+
76+
expect(config.forwardingEnabled).toBe(true);
77+
expect(config.trustProxy).toBe(false);
78+
79+
expect(() =>
80+
loadConfig({
81+
STACKFLOW_NODE_FORWARDING_ENABLED: 'maybe',
82+
}),
83+
).toThrow(/STACKFLOW_NODE_FORWARDING_ENABLED must be a boolean/);
84+
});
85+
7086
it('rejects stackflow-node ports outside the TCP range', () => {
7187
expect(() =>
7288
loadConfig({

0 commit comments

Comments
 (0)