Environment variable and configuration audit. 8 dimensions (ENV1-ENV8): variable completeness, unused vars, startup validation, secret exposure, environment parity, type safety, default values, and documentation. Supports Node.js (.env/process.env), Python (os.environ/settings), and framework-specific patterns (Vite import.meta.env, Next.js NEXT_PUBLIC_). Switches: zuvo:env-audit full | [path] | --secrets-only | --parity
Scanned 9/12/2026
Install to Claude Code
npx -y skills add aibot88/sec_skill_store --skill env-audit --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Env Audit?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/aibot88-env-audit)More formats (shields.io, HTML) on the badges page.
---
name: env-audit
description: >
Environment variable and configuration audit. 8 dimensions (ENV1-ENV8):
variable completeness, unused vars, startup validation, secret exposure,
environment parity, type safety, default values, and documentation. Supports
Node.js (.env/process.env), Python (os.environ/settings), and framework-specific
patterns (Vite import.meta.env, Next.js NEXT_PUBLIC_).
Switches: zuvo:env-audit full | [path] | --secrets-only | --parity
codesift_tools:
always:
- analyze_project
- index_status
- index_folder
- index_file
- plan_turn
- scan_secrets # KEY — ENV4 secret exposure (committed .env, hardcoded values)
- get_file_tree # discover .env*, config/*, settings.py
- search_text # process.env.X, os.environ.get, import.meta.env.X
- search_patterns # ENV2 unused, ENV3 missing-validation, ENV6 type-coerce
- get_file_outline
- find_references # ENV2 unused — where is each var actually read
- search_symbols
- get_symbol
- audit_scan
by_stack:
typescript: [get_type_info]
javascript: []
python: [python_audit, analyze_async_correctness]
php: [php_project_audit, php_security_scan]
kotlin: [analyze_sealed_hierarchy, find_extension_functions, trace_flow_chain, trace_suspend_chain, trace_compose_tree, analyze_compose_recomposition, trace_hilt_graph, trace_room_schema, analyze_kmp_declarations, extract_kotlin_serialization_contract]
nestjs: [nest_audit]
nextjs: [framework_audit, nextjs_route_map]
astro: [astro_audit, astro_actions_audit, astro_hydration_audit]
hono: [analyze_hono_app, audit_hono_security]
express: []
fastify: []
react: [react_quickstart, analyze_hooks, analyze_renders]
django: [analyze_django_settings, effective_django_view_security, taint_trace]
fastapi: [trace_fastapi_depends, get_pydantic_models]
flask: [find_framework_wiring]
jest: []
yii: [resolve_php_service]
prisma: [analyze_prisma_schema]
drizzle: []
sql: [sql_audit]
postgres: [migration_lint]
---
# zuvo:env-audit
Audit environment variable management for completeness, safety, and consistency
across development, staging, and production environments. Single-pass execution.
**When to use:** After adding new env vars, before production deploy, when
debugging config-related incidents, onboarding new environments, periodic check.
**When NOT to use:** Secret scanning in source code (`/security-audit`), CI
pipeline config (`zuvo:ci-audit`), infrastructure (`/security-audit`).
## Mandatory File Loading
Read every file below before starting. Print the checklist.
```
CORE FILES LOADED:
1. ../../shared/includes/codesift-setup.md -- [READ | MISSING -> STOP]
2. ../../shared/includes/env-compat.md -- [READ | MISSING -> STOP]
3. ../../shared/includes/run-logger.md -- [READ | MISSING -> STOP]
4. ../../shared/includes/retrospective.md -- [READ | MISSING -> STOP]
```
If any file is MISSING, STOP. Do not proceed from memory.
---
## Argument Parsing
| Token | Behavior |
|-------|----------|
| _(empty)_ or `full` | All 8 dimensions, scope = project root |
| `[path]` | Scope to a directory (monorepo package, module) |
| `--secrets-only` | ENV4 only -- focus on secret exposure |
| `--parity` | ENV5 only -- compare environment configurations |
---
## Safety Gates
### GATE 1 -- Read-Only
This audit is **read-only**. The only write target is `audits/`.
FORBIDDEN:
- Modifying any `.env`, config, or source file
- Creating or deleting environment variables
- Accessing external secret managers (analyze config references only)
### GATE 2 -- PII and Secret Censorship
When including env var values in the report:
- Replace actual values with `***`
- Show only variable names, never values
- Exception: `.env.example` placeholder values can be shown as-is
---
## Phase 0: Detect and Scope
### 0.1 Target Resolution
Set `TARGET_ROOT` from arguments. All subsequent commands use this path.
### 0.2 Stack Detection
Detect which env access patterns to search for:
| Signal | Stack | Access Pattern |
|--------|-------|----------------|
| `package.json` | Node.js | `process.env.VAR`, `process.env['VAR']` |
| `vite.config.*` | Vite | `import.meta.env.VITE_VAR` |
| `next.config.*` | Next.js | `process.env.NEXT_PUBLIC_VAR` (client-safe) |
| `requirements.txt`, `pyproject.toml` | Python | `os.environ['VAR']`, `os.getenv('VAR')`, `os.environ.get('VAR')` |
| `wrangler.toml` | Cloudflare Workers | `env.VAR` (binding) |
| `docker-compose.*` | Docker Compose | `environment:` / `env_file:` directives |
### 0.3 Variable Name Extraction
Build a comprehensive inventory by scanning code for all env access patterns
detected in 0.2. Collect unique variable names (not values).
### 0.4 Inventory
Cross-reference all sources:
1. Variables defined in `.env*` files (names only)
2. Variables accessed in code (from extraction)
3. Variables in Docker Compose, CI config, or cloud deployment files
Print:
```
ENV VAR INVENTORY
------------------------------------
Target: [TARGET_ROOT]
Config sources: [.env, .env.example, .env.local, ...]
Stack: [Node.js / Python / Vite / Next.js]
Vars defined: [N]
Vars used in code: [N]
------------------------------------
```
---
## Phase 1: Dimension Analysis (ENV1-ENV8)
Single-pass inline execution. No sub-agents required.
### ENV1: Variable Completeness -- Weight 15, Max 15, Critical Gate
Are all required env vars defined and documented?
| Check | Good | Bad | Severity |
|-------|------|-----|----------|
| Code vs example coverage | All used vars listed in `.env.example` | Var used in code but missing from `.env.example` | CRITICAL |
| Required vars guarded | Startup fails fast on missing required var | App starts, crashes later when var accessed | CRITICAL |
| .env.example exists | Yes, with all vars and placeholder values | No `.env.example` at all | HIGH |
| Stale vars | No vars in `.env.example` that are unused in code | Accumulated dead config | MEDIUM |
**Detection approach:** Compare the set of variables found in code against the
set defined in `.env.example`. Report both directions: used-but-undocumented
and documented-but-unused.
Critical gate: ENV1=0 (no `.env.example` OR multiple critical vars
undocumented) triggers FAIL.
### ENV2: Unused Variables -- Weight 5, Max 5
| Check | Good | Bad | Severity |
|-------|------|-----|----------|
| Defined but never used | All defined vars referenced in code | Stale vars accumulate | MEDIUM |
| Dead config sections | Removed features cleaned up | Config references removed features | LOW |
**Detection approach:** Check each var in `.env.example` against code access
patterns. A var referenced only in comments or documentation counts as unused.
### ENV3: Startup Validation -- Weight 15, Max 15, Critical Gate
Does the app fail fast on missing or invalid configuration?
| Check | Good | Bad | Severity |
|-------|------|-----|----------|
| Schema validation | Zod/Joi/Pydantic/envalid validates all vars at startup | No validation, raw access everywhere | CRITICAL |
| Fail-fast behavior | App throws before serving requests on missing var | Starts, breaks when var first accessed | CRITICAL |
| Type coercion | Parsed and validated (parseInt, Number, schema) | Port used as string in numeric context | HIGH |
**What to search for:**
- Validation libraries: `z.object`, `Joi.object`, `BaseSettings`, `envalid`,
`envsafe`, `convict`, `cleanEnv`, `ConfigModule`, `@IsString`
- Fail-fast patterns: `throw` with env/config message, `process.exit` on
missing env
- Raw usage: `process.env.VAR` without validation wrapper
- NestJS: `ConfigModule.forRoot()` with `validationSchema`
Critical gate: ENV3=0 (no validation anywhere) triggers FAIL.
### ENV4: Secret Exposure -- Weight 15, Max 15, Critical Gate
| Check | Good | Bad | Severity |
|-------|------|-----|----------|
| .env in .gitignore | `.env` and `.env.local` listed in .gitignore | .env committed to repository | CRITICAL |
| Client-side exposure | Only `NEXT_PUBLIC_`/`VITE_` vars are non-sensitive | API keys in `NEXT_PUBLIC_*` variables | CRITICAL |
| Hardcoded secrets | Secrets only in env vars or secret manager | API keys hardcoded in config files | CRITICAL |
| Docker image leakage | `.env` listed in .dockerignore | .env copied into Docker image | HIGH |
| Git history | No .env files in git history | Past .env commits (secrets may still be in history) | HIGH |
**Detection approach:**
- Check `.gitignore` for `.env` patterns
- `git ls-files` for committed `.env` files
- Search for `NEXT_PUBLIC_*KEY`, `NEXT_PUBLIC_*SECRET`, `VITE_*KEY` etc.
- Search for hardcoded secrets: `(api_key|secret|password|token)\s*[:=]\s*['"][^'"{}$]`
excluding test/mock/example files
- Check `.dockerignore` for `.env`
Critical gate: ENV4=0 (secrets committed or API keys in client-side vars)
triggers FAIL.
### ENV5: Environment Parity -- Weight 10, Max 10
Are dev/staging/production environments consistent?
| Check | Good | Bad | Severity |
|-------|------|-----|----------|
| Same var set across envs | All environments define the same variables | Prod has vars dev lacks | HIGH |
| Docker Compose alignment | `docker-compose` env matches app config | Docker sets vars not in `.env.example` | MEDIUM |
| Feature flag documentation | Feature flags documented per environment | Undocumented per-env behavior | MEDIUM |
**Detection approach:** Compare variable sets across all `.env*` files found
at the target root. Check Docker Compose `environment:` sections against the
`.env.example` inventory.
### ENV6: Type Safety -- Weight 8, Max 8
| Check | Good | Bad | Severity |
|-------|------|-----|----------|
| Central config module | Typed config with validated accessors | Raw `process.env.X` scattered across 50+ files | HIGH |
| Boolean parsing | `=== 'true'` or schema-parsed | `if (process.env.FLAG)` (any string is truthy) | HIGH |
| Number parsing | `parseInt` with NaN check or schema | Port used as string | MEDIUM |
| Access centralization | < 3 files access `process.env` directly | 20+ files with raw env access | MEDIUM |
**Detection approach:** Count per-file occurrences of raw env access. Search
for boolean truthy gotchas (`if (process.env.VAR)`). Check for number
conversion patterns.
### ENV7: Default Values -- Weight 7, Max 7
| Check | Good | Bad | Severity |
|-------|------|-----|----------|
| Dev-friendly defaults | `PORT ?? 3000` for local development | App crashes without complete `.env` | MEDIUM |
| No secret defaults | Secret vars always required, no fallback | `JWT_SECRET ?? 'default-secret'` | CRITICAL |
| Documented defaults | `.env.example` shows which vars have defaults | Developer cannot tell required vs optional | LOW |
**Detection approach:**
- Search for `??` and `||` operators after env access (Node.js)
- Search for `os.getenv('VAR', 'default')` patterns (Python)
- Flag any default on SECRET/KEY/PASSWORD/TOKEN variables
### ENV8: Documentation -- Weight 5, Max 5
| Check | Good | Bad | Severity |
|-------|------|-----|----------|
| Inline comments | Each var in `.env.example` has a comment explaining purpose | Bare variable names only | MEDIUM |
| Required vs optional | Marked with `# Required` / `# Optional (default: 3000)` | No indication which vars are needed | MEDIUM |
| Setup instructions | README covers env setup | New developer has to guess | LOW |
---
## Phase 2: Scoring
```
ENV1 = [0-15] Variable Completeness (critical gate)
ENV2 = [0-5] Unused Variables
ENV3 = [0-15] Startup Validation (critical gate)
ENV4 = [0-15] Secret Exposure (critical gate)
ENV5 = [0-10] Environment Parity
ENV6 = [0-8] Type Safety
ENV7 = [0-7] Default Values
ENV8 = [0-5] Documentation
```
**Score = sum / 80 x 100**
**Critical gates:** ENV1=0 OR ENV3=0 OR ENV4=0 triggers FAIL.
| Grade | Percentage |
|-------|-----------|
| HEALTHY | >= 80% |
| NEEDS ATTENTION | 60-79% |
| AT RISK | 40-59% |
| CRITICAL | < 40% |
---
## Phase 3: Report
Save to: `audits/env-audit-[YYYY-MM-DD].md`
### Report Structure
```markdown
# Environment & Configuration Audit Report
## Metadata
| Field | Value |
|-------|-------|
| Project | [name] |
| Date | [YYYY-MM-DD] |
| Scope | [TARGET_ROOT or full project] |
| Stack | [Node.js / Python / Vite / Next.js] |
| Config sources | [.env, .env.example, config.ts, ...] |
| Vars defined | [N] |
| Vars used in code | [N] |
## Executive Summary
**Score: [N] / 100** -- [HEALTHY / NEEDS ATTENTION / AT RISK / CRITICAL]
| Metric | Count |
|--------|-------|
| CRITICAL findings | N |
| HIGH findings | N |
| MEDIUM findings | N |
[2-3 sentence summary]
## Dimension Scores
| # | Dimension | Score | Max | Notes |
|---|-----------|-------|-----|-------|
| ENV1 | Variable Completeness | [N] | 15 | |
| ENV2 | Unused Variables | [N] | 5 | |
| ENV3 | Startup Validation | [N] | 15 | |
| ENV4 | Secret Exposure | [N] | 15 | |
| ENV5 | Environment Parity | [N] | 10 | |
| ENV6 | Type Safety | [N] | 8 | |
| ENV7 | Default Values | [N] | 7 | |
| ENV8 | Documentation | [N] | 5 | |
| **Total** | | **[N]** | **80** | |
## Variable Coverage Matrix
| Variable | .env.example | Code Usage | Validated | Type | Default | Secret? |
|----------|-------------|------------|-----------|------|---------|---------|
| DATABASE_URL | Y | Y (3 files) | Y (Zod) | string | none (required) | Y |
| PORT | Y | Y (1 file) | N (raw) | number | 3000 | N |
| ... | | | | | | |
## Findings (sorted by severity)
[Per finding: dimension, severity, file:line, description, fix]
## Remediation Roadmap
### Quick Wins (< 1 hour)
### Short-term (1 day)
### Medium-term (1 week)
```
### Report Validation
After writing, verify:
- Dimension scores sum to total
- Finding counts match Executive Summary
- Variable Coverage Matrix includes all discovered vars
- No actual secret values appear anywhere in the report (Gate 2)
---
## Phase 4: Next-Step Routing
```
RECOMMENDED NEXT ACTION
------------------------------------
ENV4 CRITICAL (secrets exposed) -> /security-audit --static
ENV3 = 0 (no validation) -> add config schema (Zod/Joi/Pydantic)
ENV1 = 0 (no .env.example) -> create .env.example from code usage
ENV7 secret defaults -> remove default values from secret vars
Score < 60% -> fix critical gates, re-audit
Score >= 80% -> schedule next audit in 3 months
------------------------------------
```
---
## ENV-AUDIT COMPLETE
Score: [N] / 100 -- [grade]
Stack: [Node.js / Python / Vite / Next.js]
Dimensions: [N scored] | Critical gates: [PASS/FAIL]
Findings: [N critical] / [N total]
Run: <ISO-8601-Z> env-audit <project> <N-critical> <N-total> <VERDICT> - <N>-dimensions <NOTES> <BRANCH> <SHA7> <INCLUDES> <TIER>
### Retrospective (REQUIRED)
Follow the retrospective protocol from `retrospective.md`.
Gate check → structured questions → TSV emit → markdown append.
If gate check skips: print "RETRO: skipped (trivial session)" and proceed.
After printing this block, append the `Run:` line value (without the `Run: ` prefix) to the log file path resolved per `run-logger.md`.
VERDICT: PASS (0 critical findings), WARN (1-3 critical), FAIL (4+ critical).
---
## Execution Notes
- Single-pass inline execution, no sub-agents required
- All commands use the resolved `TARGET_ROOT` from argument parsing
- Stack detection auto-selects the correct env access patterns (Node.js,
Python, Vite, Next.js)
- NestJS projects: check `ConfigModule.forRoot()` and `ConfigService` usage
patterns specifically
- Docker Compose: check both `environment:` and `env_file:` directives
- Monorepos: use `[path]` argument to scope to a specific package
- CodeSift is minimally used in this skill (env patterns are best found via
grep), but `codesift-setup.md` is loaded for consistency
- The boolean truthy gotcha (`if (process.env.FLAG)` -- always true for any
non-empty string) is a common source of production bugs; flag it prominently
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!