agentleFS
greglas75greglas75/zuvo/skills/env-audit/SKILL.md

env-audit

Skill7 starsChanged 27 days agoMITView on GitHub

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

  • Reads credentials

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

19 rules. About 0 are common, 7 are distinctive, and 12 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈7

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 12

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When to use: After adding new env vars, before production deploy, when debugging config-related incidents, onboarding new environments, …

    line 56
  2. Distinctive:

    Read every file below before starting.

    line 63
  3. Distinctive:

    If any file is MISSING, STOP.

    line 73
  4. Distinctive:

    When including env var values in the report:

    line 101
  5. Very specific:

    Use the deterministic preload helper FIRST: Run ~/.zuvo/compute-preload env-audit "$PWD".

    line 128
  6. Very specific:

    Detection approach: Check each var in .env.example against code access patterns.

    line 206
  7. Very specific:

    Check .gitignore for .env patterns

    line 240
  8. Very specific:

    Search for NEXT_PUBLIC_*KEY, NEXT_PUBLIC_*SECRET, VITE_*KEY etc.

    line 242
  9. Very specific:

    Search for hardcoded secrets: (api_key|secret|password|token)\s*[:=]\s*['"][^'"{}$] excluding test/mock/example files

    line 243
  10. Very specific:

    Check .dockerignore for .env

    line 245
  11. Very specific:

    Search for ?? and || operators after env access (Node.js)

    line 285
  12. Very specific:

    Search for os.getenv('VAR', 'default') patterns (Python)

    line 286
  13. Distinctive:

    No actual secret values appear anywhere in the report (Gate 2)

    line 406
  14. Very specific:

    If gate_status = FAIL → VERDICT = INCOMPLETE.

    line 451
  15. Distinctive:

    Run: <ISO-8601-Z> env-audit <project> <N-critical> <N-total> <VERDICT> - <N>-dimensions <NOTES> <BRANCH> <SHA7> <INCLUDES> <TIER>

    line 459
  16. Very specific:

    Follow the retrospective protocol from retrospective.md.

    line 464
  17. Distinctive:

    If gate check skips: print "RETRO: skipped (trivial session)" and proceed.

    line 466
  18. Very specific:

    After printing this block, append the Run: line value (without the Run: prefix) to the log file path resolved per run-logger.md.

    line 468
  19. Very specific:

    All commands use the resolved TARGET_ROOT from argument parsing

    line 477

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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

TokenBehavior
(empty) or fullAll 8 dimensions, scope = project root
[path]Scope to a directory (monorepo package, module)
--secrets-onlyENV4 only -- focus on secret exposure
--parityENV5 only -- compare environment configurations

Safety Gates

GATE 1 -- Read-Only

This audit is read-only. The only write target is zuvo/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.

ToolTriggerSkip allowed?
scan_secretsAlwaysNO — KEY: ENV4 secret exposure
search_textAlwaysNO — process.env / os.environ / import.meta.env scan
search_patternsAlwaysNO — ENV2/ENV3/ENV6 anti-patterns
find_referencesAlwaysNO — ENV2 unused detection
audit_scanAlwaysNO — 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:

SignalStackAccess Pattern
package.jsonNode.jsprocess.env.VAR, process.env['VAR']
vite.config.*Viteimport.meta.env.VITE_VAR
next.config.*Next.jsprocess.env.NEXT_PUBLIC_VAR (client-safe)
requirements.txt, pyproject.tomlPythonos.environ['VAR'], os.getenv('VAR'), os.environ.get('VAR')
wrangler.tomlCloudflare Workersenv.VAR (binding)
docker-compose.*Docker Composeenvironment: / 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?

CheckGoodBadSeverity
Code vs example coverageAll used vars listed in .env.exampleVar used in code but missing from .env.exampleCRITICAL
Required vars guardedStartup fails fast on missing required varApp starts, crashes later when var accessedCRITICAL
.env.example existsYes, with all vars and placeholder valuesNo .env.example at allHIGH
Stale varsNo vars in .env.example that are unused in codeAccumulated dead configMEDIUM

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: no .env.example OR multiple critical vars undocumented → triggers FAIL. The gate fires on that FINDING; an ENV1 score of 0 also fires it but is not required.

ENV2: Unused Variables -- Weight 5, Max 5

CheckGoodBadSeverity
Defined but never usedAll defined vars referenced in codeStale vars accumulateMEDIUM
Dead config sectionsRemoved features cleaned upConfig references removed featuresLOW

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?

CheckGoodBadSeverity
Schema validationZod/Joi/Pydantic/envalid validates all vars at startupNo validation, raw access everywhereCRITICAL
Fail-fast behaviorApp throws before serving requests on missing varStarts, breaks when var first accessedCRITICAL
Type coercionParsed and validated (parseInt, Number, schema)Port used as string in numeric contextHIGH

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: no validation anywhere → triggers FAIL. The gate fires on that FINDING; an ENV3 score of 0 also fires it but is not required.

ENV4: Secret Exposure -- Weight 15, Max 15, Critical Gate

CheckGoodBadSeverity
.env in .gitignore.env and .env.local listed in .gitignore.env committed to repositoryCRITICAL
Client-side exposureOnly NEXT_PUBLIC_/VITE_ vars are non-sensitiveAPI keys in NEXT_PUBLIC_* variablesCRITICAL
Hardcoded secretsSecrets only in env vars or secret managerAPI keys hardcoded in config filesCRITICAL
Docker image leakage.env listed in .dockerignore.env copied into Docker imageHIGH
Git historyNo .env files in git historyPast .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: secrets committed or API keys in client-side vars → triggers FAIL. The gate fires on that FINDING; an ENV4 score of 0 also fires it but is not required.

ENV5: Environment Parity -- Weight 10, Max 10

Are dev/staging/production environments consistent?

CheckGoodBadSeverity
Same var set across envsAll environments define the same variablesProd has vars dev lacksHIGH
Docker Compose alignmentdocker-compose env matches app configDocker sets vars not in .env.exampleMEDIUM
Feature flag documentationFeature flags documented per environmentUndocumented per-env behaviorMEDIUM

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

CheckGoodBadSeverity
Central config moduleTyped config with validated accessorsRaw process.env.X scattered across 50+ filesHIGH
Boolean parsing=== 'true' or schema-parsedif (process.env.FLAG) (any string is truthy)HIGH
Number parsingparseInt with NaN check or schemaPort used as stringMEDIUM
Access centralization< 3 files access process.env directly20+ files with raw env accessMEDIUM

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

CheckGoodBadSeverity
Dev-friendly defaultsPORT ?? 3000 for local developmentApp crashes without complete .envMEDIUM
No secret defaultsSecret vars always required, no fallbackJWT_SECRET ?? 'default-secret'CRITICAL
Documented defaults.env.example shows which vars have defaultsDeveloper cannot tell required vs optionalLOW

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

CheckGoodBadSeverity
Inline commentsEach var in .env.example has a comment explaining purposeBare variable names onlyMEDIUM
Required vs optionalMarked with # Required / # Optional (default: 3000)No indication which vars are neededMEDIUM
Setup instructionsREADME covers env setupNew developer has to guessLOW

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 — any ONE of these findings triggers FAIL, whatever the numeric score:

GateFires on
ENV1no .env.example, OR multiple critical vars undocumented
ENV3no startup validation anywhere
ENV4any secret committed to the repo, OR an API key in a client-side (NEXT_PUBLIC_/VITE_) var, OR a hardcoded secret

These gates fire on the FINDING, not on an aggregate score of zero. Read as "the dimension scored 0" they are nearly unreachable, because every one of them spreads several checks over its weight: ENV4 carries five checks across 15 points, so a repo with a committed .env but a correct .gitignore, no client-side leak and no hardcoded key still scores about 12/15 — and the committed secret never blocks the audit. The named condition in each parenthesis IS the trigger — a dimension score of 0 remains sufficient to fire the gate, it is simply not necessary.

GradePercentage
HEALTHY>= 80%
NEEDS ATTENTION>= 60% and < 80%
AT RISK>= 40% and < 60%
CRITICAL< 40%

Phase 3: Report

Save to: zuvo/audits/env-audit-[YYYY-MM-DD].md — at the project root (zuvo/ resolves via git rev-parse --show-toplevel; override $ZUVO_OUTPUT_DIR. See ../../shared/includes/report-output-location.md).

Report Structure

# 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):

printf '%b\n' "$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

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.