v0.1.0 · zero runtime dependencies
Catch the config bug that lives between environments.
Most env libraries validate the one process.env your app starts with. EnvGuard also checks what changes between development, staging and production, and fails the build before the deploy does.
npm install --save-dev m2envguard- error
compare-missing-in-targetSENTRY_DSN is set in staging but missing in production (required by the schema)
- error
missing-requiredSENTRY_DSN is required in production but is not set
- error
missing-requiredSTRIPE_SECRET_KEY is required when PAYMENTS_PROVIDER is "stripe" but is not set
- error
policy-value-mismatchREQUEST_TIMEOUT_MS in production is "30000" but policy requires "5000"
Real output from the library, for staging → production. No values printed.
Why EnvGuard
The failures that page people happen between files.
A payment key present in staging and absent in production. DEBUG=true shipped to prod. A server secret named NEXT_PUBLIC_…. Ten variables nothing reads any more.
A schema that knows environments
Typed fields with defaults and transforms, plus requiredIn, requiredWhen, requires, conflictsWith and deprecations.
Drift between environments
Compare staging with production under a policy: missing keys, approved values, flag drift and secrets shared across environments.
Code and config, reconciled
Finds variables the code reads that nobody declared, and ones declared that nothing reads, with honest confidence levels.
Production hygiene
Debug flags, loopback and staging endpoints, plain http, disabled TLS, placeholder secrets and test credentials in production.
Secrets stay secret
Findings carry names and locations, never values. Format matches are labelled likely. No network, no telemetry.
Built for CI
Stable exit codes, JSON reports, thresholds, strict and warn-only modes, and baselines so you adopt it without a wall of red.
How it works
Describe it once. Run it everywhere.
- 1
Validate in your app
Parse the environment at boot and get inferred types, or an error that never leaks a secret.
tsimport { createEnvGuard } from "m2envguard"; const guard = createEnvGuard({ schema: { API_URL: { type: "url", protocols: ["https"] }, PORT: { type: "port", default: 3000 }, SENTRY_DSN: { type: "url", requiredIn: ["production"] }, }, }); const env = guard.parse(process.env, { environment: "production" }); env.PORT; // number - 2
Describe your environments
The same schema in JSON, plus the files, the pairs to compare and a policy. Read as data, never executed.
json{ "schema": { "API_URL": { "type": "url" }, "SENTRY_DSN": { "type": "url", "requiredIn": ["production"] } }, "environments": { "staging": ".env.staging", "production": [".env.production"] }, "compare": { "pairs": [["staging", "production"]], "policy": { "expect": { "REQUEST_TIMEOUT_MS": 5000 } } } } - 3
Gate the pull request
Run the audit in CI. A baseline lets you fail only on new findings.
shnpx envguard audit npx envguard compare staging production npx envguard baseline -o envguard-baseline.jsonyaml- run: npx envguard audit --baseline envguard-baseline.json --fail-on warning
Playground
Edit the files. The verdict follows as you type.
This runs the envguard source from this repository in your browser. Nothing is mocked and nothing is uploaded.
- error
compare-missing-in-targetproduction - error
missing-requiredproduction - error
missing-requiredproduction - error
policy-value-mismatchproduction - warning
undeclared-reference - warning
compare-missing-in-targetproduction - warning
dev-endpoint-in-productionlikelyproduction
A key that exists in staging is missing in production, and one value was left on a staging host. How confidence works →
The code behind this panel
import {
buildReport,
compareEnvironments,
parseDotenv,
scanSource,
type Finding,
type Schema,
} from "m2envguard";
export const schema: Schema = {
API_URL: { type: "url", protocols: ["https"] },
DATABASE_URL: { type: "url", secret: true },
PORT: { type: "port", default: 3000 },
REQUEST_TIMEOUT_MS: { type: "integer", min: 100, max: 60000, default: 5000 },
SENTRY_DSN: { type: "url", requiredIn: ["production"] },
PAYMENTS_PROVIDER: { type: "enum", values: ["none", "stripe"], default: "none" },
STRIPE_SECRET_KEY: {
type: "string",
secret: true,
requiredWhen: { var: "PAYMENTS_PROVIDER", equals: "stripe" },
},
};
export function check(input: { staging: string; production: string; source: string }) {
const staging = parseDotenv(input.staging).values;
const production = parseDotenv(input.production).values;
// Validation and security checks on production run as part of the comparison.
const { findings } = compareEnvironments(
{ name: "staging", values: staging },
{ name: "production", values: production },
{ schema, policy: { expect: { REQUEST_TIMEOUT_MS: 5000 }, flags: ["FEATURE_*"] } },
);
// Variables the code reads that nobody declared.
const known = new Set([...Object.keys(schema), ...Object.keys(staging), ...Object.keys(production)]);
const undeclared: Finding[] = scanSource(input.source)
.references.filter((ref) => !known.has(ref.name))
.map((ref) => ({
rule: "undeclared-reference",
severity: "warning",
confidence: "confirmed",
variable: ref.name,
message: `${ref.name} is read in code but is not in the schema or any env file`,
location: { file: "src/server.ts", line: ref.line },
}));
return buildReport([...findings, ...undeclared]);
}Trustworthy by design
A red build should mean something specific.
Every finding states how sure the analysis is, so heuristics never pose as facts.
- Confirmed
Follows directly from the input: a missing key, a failed parse, a literal reference.
- Likely
A heuristic matched, such as a value in the format of a known token. Reported, labelled, never claimed as live.
- Uncertain
Static analysis cannot rule out a legitimate use. These never fail a run unless you pass --strict.
Documentation
Everything is written down.
More from M2Toolkit