Skip to content
Back to skills

Env Audit

ASecurity

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

  • 8 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 28, 2026
developmentjavascripttypescriptpythongojavaphpkotlinbashsqlreact

Works with

  • cli
  • api

Security analysis

A100/100

Scanned May 28, 2026

npx -y skills add greglas75/zuvo --skill env-audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Env Audit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Env Audit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/greglas75-env-audit/badge)](https://www.skillsdirectory.com/skills/greglas75-env-audit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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, resolve_constant_value]
    javascript: []
    python: [python_audit, analyze_async_correctness, resolve_constant_value]
    php: [php_project_audit, php_security_scan, resolve_php_namespace]
    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, astro_sessions]
    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

---

## MANDATORY TOOL CALLS — Env Audit Validity Gate

**INVALID if any tool below is skipped.** "DEFERRED", "N/A", "--secrets-only" NOT valid reasons.

| Tool | Trigger | Skip allowed? |
|------|---------|---------------|
| `scan_secrets` | Always | **NO** — KEY: ENV4 secret exposure |
| `search_text` | Always | **NO** — process.env / os.environ / import.meta.env scan |
| `search_patterns` | Always | **NO** — ENV2/ENV3/ENV6 anti-patterns |
| `find_references` | Always | **NO** — ENV2 unused detection |
| `audit_scan` | Always | **NO** — compound check |

Forbidden: `scan_secrets: skipped`, `codesift: unavailable` (when deferred), `retrospective: skipped` — all REJECTED.

POSTAMBLE: report on disk → retro appended → `~/.zuvo/append-runlog` exit 0. Every ENV finding needs `path/to/file.ext:LINE` (verify-audit gate).

```
Mandatory-tools-acknowledgment: I will run scan_secrets + search_text + search_patterns + find_references + audit_scan for this env audit. Every ENV finding will cite a `path/to/file.ext:LINE` resolving in the current tree.
```

**Use the deterministic preload helper FIRST.** Run `~/.zuvo/compute-preload env-audit "$PWD"`. Copy `[CodeSift matching trace]` verbatim, issue printed `ToolSearch(query="select:...")`. Math gate enforced.

---

## 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]

### Validity Gate (REQUIRED — print BEFORE Run line, AFTER retro append + append-runlog)

```
VALIDITY GATE
  triggers_held: language=<X> framework=<X>
  required_tool_calls:
    scan_secrets: [<N> hits | NOT_CALLED — VIOLATES_TRIGGER]
    search_text: [<N> env-var refs | NOT_CALLED — VIOLATES_TRIGGER]
    search_patterns: [<N> hits | NOT_CALLED — VIOLATES_TRIGGER]
    find_references: [<N> ref-checks | NOT_CALLED — VIOLATES_TRIGGER]
    audit_scan: [<N> findings | NOT_CALLED — VIOLATES_TRIGGER]
  postamble:
    retros_log_appended: [yes(bytes_added=N) | NOT_APPENDED]
    retros_md_appended: [yes(entry_count=N) | NOT_APPENDED]
    verify_audit_pass: [yes(<verified>/<total>) | NOT_RUN | REJECTED]
  gate_status: [PASS | FAIL — <which gates missing>]
```

If `gate_status = FAIL` → VERDICT = INCOMPLETE.

Append the Run line via the retro-gated wrapper (NOT direct `>> runs.log`):

```bash
echo -e "$RUN_LINE" | ~/.zuvo/append-runlog
```

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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.