From 0f3a6f80b783e4c79d055ad476d9e66b022b5651 Mon Sep 17 00:00:00 2001 From: jubnl Date: Sat, 25 Jul 2026 16:05:26 +0200 Subject: [PATCH] chore(server): forbid direct process.env access outside app-config --- server/eslint.config.mjs | 31 +++++++++++++++++++++++++++++++ server/src/app-config/README.md | 16 ++++++++++++++++ server/src/nest/README.md | 6 ++++++ 3 files changed, 53 insertions(+) diff --git a/server/eslint.config.mjs b/server/eslint.config.mjs index 08fbd59e4..5b76c7f8f 100644 --- a/server/eslint.config.mjs +++ b/server/eslint.config.mjs @@ -44,4 +44,35 @@ export default tseslint.config( 'prefer-const': 'warn', }, }, + { + // Config-access guard: every env read in src/ goes through src/app-config + // (readEnv() / derive functions / registerAs tokens). Direct process.env + // access is banned outside the documented exemptions below — see + // src/app-config/README.md for the rationale behind each. + files: ['src/**/*.ts'], + ignores: [ + // The config layer itself. + 'src/app-config/**', + 'src/nest/app-config/**', + // Key-material resolution (file persistence + runtime rotation), not env config. + 'src/config.ts', + // Scrubbed plugin child process — must not import app-config. + 'src/nest/plugins/runtime/plugin-host-entry.ts', + // Child-env whitelist block: the env there is an IPC channel to the sandbox. + 'src/nest/plugins/supervisor/plugin-supervisor.ts', + // Dynamic-key caps (process.env[name] with a computed name). + 'src/nest/plugins/host/daily-budget.ts', + 'src/nest/plugins/host/plugin-audit.ts', + ], + rules: { + 'no-restricted-syntax': [ + 'error', + { + selector: "MemberExpression[object.name='process'][property.name='env']", + message: + 'Read configuration via src/app-config (readEnv()/derive/tokens), not process.env. Exemptions: eslint.config.mjs + src/app-config/README.md.', + }, + ], + }, + }, ); diff --git a/server/src/app-config/README.md b/server/src/app-config/README.md index eaa4006e9..d3fcf85fe 100644 --- a/server/src/app-config/README.md +++ b/server/src/app-config/README.md @@ -74,3 +74,19 @@ ADMIN_PASSWORD, IDEMPOTENCY_TTL_SECONDS, MCP_RATE_LIMIT (request-path check). - `src/config.ts` ENCRYPTION_KEY/JWT_SECRET resolution — key material with file persistence and runtime rotation, not env config. - Standalone scripts (`reset-admin.js`, `scripts/*`) and `tests/**`. + +The exemption list is enforced by the `no-restricted-syntax` ban on +`process.env` in `eslint.config.mjs` — keep the two lists in sync. + +## Follow-up candidates (quirks pinned during the migration, deliberately NOT fixed) + +- `numberOr` (`Number(x) || d`) treats `"0"` and negative-invalid values oddly: + `PORT=0` silently becomes 3001, `TREK_PLUGIN_RPC_BURST=-5` stays -5. +- `TRUST_PROXY=0` resolves to 1 hop (`parseInt || 1`). +- APP_URL trailing-slash stripping differs per site (feeds strips one slash, + notifications strips all). +- `DEMO_ADMIN_EMAIL` defaults differ (demo-seed: admin@trek.app, demo-reset: + admin@nomad.app). +- NODE_ENV comparisons are case-sensitive at some sites (HSTS activation, + platform statics, spa-fallback, authService dev_mode, oidcService + frontendUrl) and case-insensitive at others. diff --git a/server/src/nest/README.md b/server/src/nest/README.md index 8d4617924..3bea1a736 100644 --- a/server/src/nest/README.md +++ b/server/src/nest/README.md @@ -33,6 +33,12 @@ mount to Nest and leaves the sibling trip routes (days, places, ...) on Express. client's `X-Idempotency-Key` on mutations, mirroring the legacy `applyIdempotency` middleware so retried writes don't double-apply. - `strangler.ts` — supports both static prefixes and `:param` pattern prefixes. +- `app-config/` — the `@nestjs/config` binding (`AppConfigModule`, global). Never + read `process.env` in a module (ESLint enforces this): inject a boot-stable + namespace via its `registerAs` token (`@Inject(mcpConfig.KEY) … ConfigType<…>`) + or read runtime-toggled values live through `RuntimeEnvService` / `readEnv()` + from `src/app-config`. The classification and invariants live in + `src/app-config/README.md`. ## Parity gotchas worth remembering