chore(server): forbid direct process.env access outside app-config

This commit is contained in:
jubnl
2026-07-25 16:05:26 +02:00
parent 3b31f4256b
commit 0f3a6f80b7
3 changed files with 53 additions and 0 deletions
+31
View File
@@ -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.',
},
],
},
},
);
+16
View File
@@ -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.
+6
View File
@@ -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