Skip to content

Add fail-fast configuration validation and standalone typecheck (#230) - #237

Open
Paranoa-dev wants to merge 1 commit into
AnchorNet-Org:mainfrom
Paranoa-dev:fix/230
Open

Add fail-fast configuration validation and standalone typecheck (#230)#237
Paranoa-dev wants to merge 1 commit into
AnchorNet-Org:mainfrom
Paranoa-dev:fix/230

Conversation

@Paranoa-dev

Copy link
Copy Markdown

Add fail-fast configuration validation and a standalone typecheck step (#230)

Summary

This PR defines and enforces the AnchorNet-Backend configuration contract:

  1. Fail-fast startup validation. validateConfig() runs before the server binds a
    port (via getConfig() / createApp() in src/app.ts). It throws a
    ConfigValidationError when a required value is missing or invalid, instead of
    booting into a silently broken or insecure state.
  2. A standalone typecheck script (tsc --noEmit) that is distinct from build
    (which emits dist/), wired into CI as its own step.

The goal of #230 is the configuration contract only. The API-key auth policy (what
a missing key means at runtime) stays owned by apiKeyAuth; this PR only decides
that a missing key is unacceptable in production and fails fast there.

Configuration inventory

Value Env var Type Default Absent-value behaviour
port PORT integer 3001 Listens on 3001
feeBps FEE_BPS integer 10 10 bps fee (already validated 0–10000)
apiKey API_KEY string unset (optional) If unset, apiKeyAuth is a no-op → open mutating access. Required in production (fail-fast).
corsOrigins CORS_ORIGIN string[] unset CORS allows all origins
bodyLimit BODY_LIMIT string "100kb" 100kb JSON body limit
maintenanceMode MAINTENANCE_MODE boolean false Maintenance mode off (mutating requests allowed)
env NODE_ENV string "development" development defaults
metricsSnapshotIntervalMs METRICS_SNAPSHOT_INTERVAL_MS integer unset No automatic metrics snapshots
idempotencyTtlMs IDEMPOTENCY_TTL_MS integer 86_400_000 24h idempotency replay window
rateLimitMax RATE_LIMIT_MAX integer 30 30 requests per window
rateLimitWindowMs RATE_LIMIT_WINDOW_MS integer 60_000 60s rate-limit window
trustProxy TRUST_PROXY bool/int/string false Do not trust X-Forwarded-*

Required vs optional decision

Kept deliberately conservative — only values whose absence changes a security posture
or makes the server undeliverable are required:

  • Required (fail-fast):
    • API_KEY when NODE_ENV=production. Without it the auth middleware degrades to a
      no-op (open mutating access); we refuse to start rather than silently downgrade
      security. This closes the fail-open gap for production, called out explicitly so it
      does not surprise existing deployments (dev/test are unaffected).
    • PORT must be a valid TCP port (1–65535); an invalid port makes the server
      undeliverable.
  • Required (sanity, non-negative): RATE_LIMIT_MAX >= 0, IDEMPOTENCY_TTL_MS >= 0.
  • Optional with safe defaults: every other value. No existing correct deployment has
    its default changed by this PR.

When a key is unset in a non-production environment, the service still starts (dev
convenience) but emits a loud console.warn that it is running with open mutating
access. The warning is suppressed under NODE_ENV=test to keep test output clean.

Why hand-written validation?

Validation is implemented directly in src/config.ts rather than via a schema library
(e.g. zod). The project intentionally keeps a lean dependency footprint (3 runtime
deps). The checks are small, reviewable, and add no runtime dependency. If the team
prefers a schema-driven approach later, swapping the internals of validateConfig is
localised to this file.

typecheck script + CI

  • package.json: added "typecheck": "tsc --noEmit" (separate from build, which
    emits dist/).
  • .github/workflows/ci.yml: added a dedicated Typecheck step between Lint and
    Build so type errors are surfaced independently of the emit build.

Test plan

  • src/config.test.ts extended with a validateConfig suite:
    • valid config returned unchanged;
    • API_KEY required + throws in production, allowed in development/test;
    • out-of-range / non-integer PORT throws;
    • negative RATE_LIMIT_MAX and IDEMPOTENCY_TTL_MS throw.
  • npm run typecheck, npm run lint, and npm test (497 tests) all pass.

closes #230

…rg#230)

- validateConfig() enforces required values before the server binds:
  API_KEY required in production, PORT valid 1-65535, non-negative
  rate-limit/idempotency values; warns loudly (non-prod) on open access.
- Wire validateConfig into createApp/getConfig in app.ts.
- Add typecheck script (tsc --noEmit) and a distinct CI Typecheck step.
- Extend config.test.ts with a validateConfig suite.

closes AnchorNet-Org#230
@Paranoa-dev
Paranoa-dev marked this pull request as ready for review August 26, 2026 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant