Skip to content

orb(selfhost): a malformed CRON_INTERVAL_MS spins the scheduler at 1ms and nothing validates any numeric env var — PORT and the GitHub cache TTL fail the same way #9157

Description

@JSONbored

Problem

Numeric environment variables are read with bare Number(...) and validated nowhere. A wrong unit or
type — the most common operator mistake — produces NaN, and Node coerces setTimeout(fn, NaN) and
setInterval(fn, NaN) to a 1 ms delay.

src/server.ts:1172:

const intervalMs = Number(process.env.CRON_INTERVAL_MS ?? 120_000);

delayToNextWallClockBoundaryMs (src/selfhost/cron-alignment.ts:10-13) is nowMs % intervalMs then
intervalMs - msIntoCycle, so NaN in → NaN out, and both the one-shot setTimeout and the follow-on
setInterval (src/server.ts:1193-1195) fire at 1 ms. intervalMs = 0 takes the same path
(x % 0 → NaN).

Trigger: an operator writes CRON_INTERVAL_MS=2m, =120s, =120_000, or =0 to "disable" the
cron. Result: ~1000 scheduled ticks per second instead of one per two minutes, each running the full
worker.scheduled() fan-out — sweep dispatch, ops-alerts, reconciliation, registry refresh. Queue
flooded with duplicate maintenance jobs, GitHub REST budget burned, AI spend follows.

Same unguarded shape elsewhere:

  • src/server.ts:958PORT NaN → binds a random port, breaking the compose healthcheck.
  • src/server.ts:729GITHUB_CACHE_TTL_SECONDS NaN → Math.max(0, NaN) = NaN → > 0 is false → the
    GitHub response cache silently disables itself, quietly multiplying API usage.

preflightEnv (src/selfhost/preflight.ts:156-227) checks only REDIS_URL, the GitHub App pair,
DATABASE_URL, and secret strength. It validates no numeric var's type or range, so nothing stops boot.

Impact

A single-character config mistake produces a runaway that burns rate limit and AI spend, with no boot-time
error and no alert naming the cause. The cache case is worse in a way — it degrades silently and looks
like ordinary load.

Requirements

  • Add a positiveInteger(name, min, max) helper to preflight.ts and run CRON_INTERVAL_MS, PORT,
    GITHUB_CACHE_TTL_SECONDS, and the *_MS admission/liveness knobs through it. Fail boot loudly on a
    bad value rather than coercing.
  • Clamp intervalMs to a floor at the call site as defence in depth.
  • Sweep for other bare Number(process.env...) reads and bring them under the same helper.
  • If CRON_INTERVAL_MS=0 should mean "disable", implement that explicitly rather than letting it fall
    into the NaN path.

Test Coverage Requirements

99%+ patch coverage, branch-counted; both arms per validated var, including the 0 and unit-suffix cases.

Links & Resources

maintainer-only — configuration safety.

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions