Skip to content
Sarj Code Standards

no-raw-env

Disallow direct process.env and import.meta.env reads outside validated boundaries.

Why

Raw environment reads are untyped and defer invalid configuration failures until use. One shared validated settings object gives consumers a consistent startup configuration.

Fix

Define a Zod settings schema, call SettingsSchema.parse(process.env) once at startup, and export or inject that single parsed settings object. Use Pydantic/Pydantic Settings for the equivalent Python boundary. Required values must fail before remote effects; preserve independent security policy and existing optional defaults.

Examples

Before — flagged An unfinished parser promise is not startup settings
src/env.ts
import { z } from "zod";
const SettingsSchema = z.object({ API_KEY: z.string().min(1) });
export const settings = SettingsSchema.parseAsync(process.env);
After — preferred Await parsing before exporting the shared settings object
src/env.ts
import { z } from "zod";
const SettingsSchema = z.object({ API_KEY: z.string().min(1) });
export const settings = await SettingsSchema.parseAsync(process.env);
Before — flagged A local identity adapter does not validate environment input
src/env.ts
const createEnv = (input) => input;
export const settings = createEnv(process.env);
After — preferred An identity adapter can forward into the actual Zod parser
src/env.ts
import { z } from "zod";
const SettingsSchema = z.object({ API_KEY: z.string().min(1) });
const createEnv = (input) => input;
export const settings = SettingsSchema.parse(createEnv(process.env));
Before — flagged JSON decoding does not validate the settings contract
src/env.ts
export const settings = JSON.parse(process.env.SETTINGS_JSON);
After — preferred Validate decoded environment JSON with the actual schema
src/env.ts
import { z } from "zod";
const SettingsSchema = z.object({ API_ORIGIN: z.url() });
export const settings = SettingsSchema.parse(JSON.parse(process.env.SETTINGS_JSON));
Before — flagged Native env import aliases still need validation
src/database.ts
import { env as raw } from "node:process";
export const databaseUrl = raw.DATABASE_URL;
After — preferred Parse the imported environment once and share settings
src/database.ts
import { settings } from "./env.js";
export const databaseUrl = settings.DATABASE_URL;
src/env.ts
import { env as raw } from "node:process";
import { z } from "zod";
const SettingsSchema = z.object({ DATABASE_URL: z.url() });
export const settings = SettingsSchema.parse(raw);
Before — flagged Declaring a schema does not validate a raw export
src/env.ts
import { z } from "zod";
const SettingsSchema = z.object({ DATABASE_URL: z.url() });
export const settings = { DATABASE_URL: process.env.DATABASE_URL };
After — preferred Parse once and reuse one settings object
src/database.ts
import { settings } from "./env.js";
export const primaryUrl = settings.DATABASE_URL;
export const replicaUrl = settings.DATABASE_URL;
src/env.ts
import { z } from "zod";
const SettingsSchema = z.object({ DATABASE_URL: z.url() });
export const settings = SettingsSchema.parse(process.env);
Before — flagged A raw conditional input is not the value reaching the parser
src/env.ts
import { z } from "zod";
const SettingsSchema = z.object({ API_ORIGIN: z.url() });
export const settings = SettingsSchema.parse(
process.env.LOAD_REMOTE ? fetch("https://example.test/config") : {},
);
After — preferred Parse the control setting before remote effects
src/env.ts
import { z } from "zod";
const SettingsSchema = z.object({
LOAD_REMOTE: z.enum(["0", "1"]).transform((value) => value === "1"),
});
export const settings = SettingsSchema.parse(process.env);
if (settings.LOAD_REMOTE) await fetch("https://example.test/config");
Before — flagged Parsing one selected field does not validate another raw export
src/env.ts
import { env as raw } from "node:process";
import { z } from "zod";
const SettingsSchema = z.object({ API_KEY: z.string().min(1) });
const { API_KEY, API_ORIGIN } = raw;
export const settings = SettingsSchema.parse({ API_KEY });
export const unparsedOrigin = API_ORIGIN;
After — preferred Validate every selected ordinary field and reuse the parsed result
src/env.ts
import { env as raw } from "node:process";
import { z } from "zod";
const SettingsSchema = z.object({ API_KEY: z.string().min(1), API_ORIGIN: z.url() });
const { API_KEY, API_ORIGIN } = raw;
export const settings = SettingsSchema.parse({ API_KEY, API_ORIGIN });