51 env config hardening
Skill FluxonLab/Skillry/plugins/security/skills/51-env-config-hardening
Installable, permission-bounded, multi-platform agent skills & subagents for Claude Code, Codex, Copilot & Gemini/Antigravity — 125 skills + 73 subagents across 18 departments, with a validation harness, native plugin marketplace, and full upstream attribution. by FluxonLab.
npx -y skills add FluxonLab/Skillry --skill 51-env-config-hardeningAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when you need to harden environment handling, config defaults, .env safety, secret loading, and redaction.
SKILL.md
14.3 KB, ~3.5k tokens by cl100k_base, as published. Nobody here has run it
Env Config Hardening
Purpose
Scope note: For committed-secret scanning and credential leak detection (git history, CORS, JWT, injection sinks), use
security-and-secrets-review(47); this skill focuses on env var validation, config defaults, and 12-factor hardening.
Audit how the project loads, validates, and protects environment variables and secrets. Catch .env files committed to git, default secrets in code, missing schema validation, missing redaction in logs, and deviations from 12-factor config principles. Produce concrete fixes with file references — not just observations.
The practical goal: the app must fail fast with a clear, actionable error when a required config value is missing or malformed, and it must never silently start with a dangerous default. Every finding includes the file and line where the risk lives and the smallest safe fix.
When to use
- A
.envfile or hardcoded secret was found (or suspected) in the codebase or git history. - The project has no config validation layer and starts silently with missing or malformed env vars.
NODE_ENVis not set or is set inconsistently across environments.- Secret rotation is being planned and you need to confirm the codebase supports it cleanly.
- A new service is being added and needs to follow existing config conventions.
- A Docker image or CI pipeline change touches how secrets are injected into the container.
When not to use
- The project uses a secrets manager (Vault, AWS Secrets Manager, GCP Secret Manager) exclusively and the task is to configure that manager, not the application code.
- Config hardening was done recently and the task is something else entirely.
- The codebase is a throwaway script with no production deployment.
Procedure
-
Check
.gitignorefor.envfiles. Confirm.env,.env.local,.env.*.local, and any environment-specific variants (*.env,.env.production,.env.staging) are excluded. Rungit ls-files | grep -E '\.env'to surface any currently tracked env files, andgit log --all --full-history -- '*.env' '**/.env'to detect past commits. -
Scan for hardcoded secrets. Search for assignment patterns:
password =,secret =,api_key =,token =,private_key, base64-encoded strings longer than 40 characters, and PEM headers (-----BEGIN). Look in config files, seed files, test fixtures, migration scripts, and Dockerfiles. Every hit must be reported asfile:line— raw values must not appear in the output. -
Verify
.env.example/.env.templateexists and is complete. The template must list every required variable with a placeholder value (not the real value), a comment describing its purpose, and an expected format. Example:DATABASE_URL=postgres://user:pass@host:5432/db # Required. Postgres connection string.. Compare the template against the actual variables consumed by the app. -
Inspect config validation. Check for a startup validation step using
zod,joi,envalid,t3-env,convict, or equivalent. The app must fail fast with a clear error message if a required variable is missing or has the wrong type — not crash withCannot read properties of undefinedat first usage, and not start silently with a broken config. -
Check
NODE_ENVhandling. ConfirmNODE_ENVis set explicitly in all deployment environments. Code that branches onNODE_ENV === 'production'for security features (HTTPS-only cookies, strict CSP, forced HTTPS) must default to the secure branch whenNODE_ENVis unset — not to the permissive development branch. -
Audit default values. Look for patterns like
process.env.SECRET || 'changeme'orprocess.env.JWT_SECRET || 'dev-secret'. Required secrets must have no fallback default. A required value that silently defaults to a known string in production is a critical misconfiguration. -
Verify secret redaction in logs. Search for logging statements that include
req.headers,req.body,process.env, or config objects without a redaction step. Confirm the logging middleware stripsauthorization,cookie,password,secret,token, andx-api-keyfield names. Check error tracking integrations (Sentry, Datadog) for similar leakage. -
Check secret rotation readiness. Secrets should be loaded at request time or at startup through a reload-capable mechanism, not cached in module-level constants in a way that prevents hot rotation. A rotating secret that requires a full process restart to take effect creates a downtime window during rotation.
-
Inspect Dockerfile and CI config. Confirm
ARGvsENVusage in Dockerfiles —ARGvalues are visible in image layer history viadocker history; secrets must not be passed asARG. In CI, secrets must be injected via the CI platform's secret store (GitHub Actionssecrets.*, GitLab CI variables, CircleCI contexts), never stored in plain text in workflow YAML files. -
Verify environment parity. Compare the variable list in
.env.exampleagainst what each deployment environment (staging, production) actually provides. Flag variables present in the example but absent from deployment configs (silently unset at runtime) and variables present in deployment but absent from the example (undocumented dependency).
Concrete checks
-
.gitignoreincludes.env,.env.local, and all.env.*.localvariants; wildcards like*.envand*.env.*are preferred over per-file entries. -
git ls-files | grep -E '\.env'returns empty; no env files are currently tracked. -
git log --all --full-history -- '*.env'returns empty; no env files exist in history. - No hardcoded passwords, API keys, or tokens appear in source code, test fixtures, seed files, or migration scripts.
-
.env.exampleexists and lists every required variable with a placeholder value and a comment. - Startup config validation fails fast on missing or invalid variables (zod/joi/envalid parse runs at import time or in the server init function, before any request handler is registered).
- No required secret has a fallback default value in code.
-
NODE_ENVdefaults to the secure branch when unset; the insecure (dev) branch is opt-in, not opt-out. - Logging middleware redacts
authorization,cookie,password,secret,token,x-api-keyfields before writing to any log sink. - No secrets passed as Docker
ARG; secrets enter containers via environment injection only (--env-fileor orchestrator-managed secrets). - CI pipeline reads secrets from the platform secret store; no plain-text secret values appear in workflow YAML files.
- All environment-specific configs (staging, prod) reconciled against
.env.example; no variable is silently unset.
Commands
# Check for tracked .env files (must return empty)
git ls-files | grep -E '(^|/)\.env($|\.)' || echo "clean"
# Check .gitignore coverage
git check-ignore -v .env .env.local .env.production 2>/dev/null || echo "WARNING: one or more .env files not gitignored"
# Search git history for .env file commits
git log --all --full-history --oneline -- '*.env' '**/.env' '.env*'
# Hardcoded secret patterns (generic assignment)
rg -niP '(password|passwd|secret|api[_-]?key|token|private[_-]?key)\s*[:=]\s*["\x27][^"\x27]{6,}' \
-g '!*.lock' -g '!node_modules' -g '!*.min.*' .
# Base64 blob and PEM header detection
rg -nP '-----BEGIN [A-Z ]*PRIVATE KEY-----|-----BEGIN CERTIFICATE-----' -g '!node_modules' .
rg -nP '[A-Za-z0-9+/]{40,}={0,2}' -g '!*.lock' -g '!node_modules' src/ | grep -v "# base64" | head -20
# Fallback default secrets (critical misconfiguration)
rg -n '\|\|\s*["\x27](dev|secret|changeme|password|test|local|default|123)' src/ --type ts --type js
# NODE_ENV security gate (should default to secure, not dev)
rg -n "NODE_ENV\s*!==?\s*['\"]production['\"]" src/ --type ts --type js
# Config validation library present?
rg -n "from 'zod'\|from 'joi'\|from 'envalid'\|from 't3-env'\|from 'convict'" src/ --type ts --type js || \
echo "No schema validation library detected — startup validation may be missing"
# Logging: check for unredacted sensitive fields
rg -n "console\.log\|logger\.(info|debug|warn|error)" src/ --type ts --type js | \
grep -E "req\.(headers|body)|process\.env|config\." | head -20
# Dockerfile: ARG used for secrets?
rg -n "^ARG\s+(SECRET|KEY|TOKEN|PASSWORD|PASS)" Dockerfile* docker-compose*.yml 2>/dev/null
# CI: plaintext secret values in workflow files?
rg -n "(password|secret|token|key)\s*:" .github/workflows/ .gitlab-ci.yml 2>/dev/null | \
grep -v '\$\{\{' | grep -v "secrets\." | grep -v "env\." | head -20
# .env.example vs actual env consumption (find all process.env usages)
rg -n 'process\.env\.\w+' src/ --type ts --type js -o | grep -oP 'process\.env\.\K\w+' | sort -u > /tmp/env_used.txt
grep -oP '^\K[A-Z_]+(?==)' .env.example 2>/dev/null | sort -u > /tmp/env_documented.txt
comm -23 /tmp/env_used.txt /tmp/env_documented.txt && echo "Above vars used but not in .env.example" || true
# Secret rotation readiness: detect module-level const caching of secrets
rg -n "^const\s+\w*(secret|key|token|password)\w*\s*=\s*process\.env" src/ --type ts --type js -i
# Check for dotenv loaded outside dev/test
rg -n "require\('dotenv'\)|import.*dotenv" src/ --type ts --type js | grep -v "test\|spec\|__tests__\|\.test\." | head -10
# Docker: verify ENV vs ARG for secrets (ENV bakes value into image layers too — use runtime injection)
grep -nE "^(ENV|ARG)\s" Dockerfile* 2>/dev/null | head -20
# Error tracking leakage: Sentry/Datadog breadcrumbs with sensitive data
rg -n "Sentry\.(captureException|captureMessage|addBreadcrumb)" src/ --type ts --type js | \
grep -E "req\.(body|headers)|password|token" | head -10
Severity rubric
| Severity | Example |
|---|---|
| Critical | .env file tracked in git (currently or in history). Hardcoded live secret in source code. Required secret has a known string as its default (` |
| High | No startup config validation — app starts silently with a broken or missing config. NODE_ENV security gate defaults to the insecure branch. Logging middleware emits req.headers or req.body without redaction. |
| Medium | .env.example missing or incomplete (undocumented required variable). Secret cached at module load time preventing rotation without restart. dotenv loaded unconditionally in production. |
| Low | A variable in .env.example has a real-looking placeholder value instead of a clearly fake one. Staging config has an extra variable not listed in the example (undocumented optional). Verbose startup log includes config object key names. |
Common issues & anti-patterns
.envin git history: even after deletion from the working tree, the file is recoverable from every clone. Requiresgit filter-repoor BFG Repo Cleaner to purge from history — and secret rotation is mandatory regardless, because history rewrite does not invalidate the value on existing clones.dotenvloaded unconditionally in production:require('dotenv').config()at the top ofserver.tscauses the production app to read from a.envfile if one exists, silently overriding injected environment variables.dotenvshould load only in development and test (if (process.env.NODE_ENV !== 'production')).- Config spread across files without a central validation point: secrets in
config/database.js,config/redis.js, andsrc/auth/jwt.jswith no single validated config object makes it impossible to audit which variables are required and which have safe defaults. console.log(config)at startup: added for debugging and left in, this logs the entire config object — including all secret values — to stdout in production.NODE_ENVused as a security gate without a default:if (process.env.NODE_ENV !== 'production') { skipAuth() }— whenNODE_ENVis unset or empty, auth is skipped. The safe pattern isif (process.env.NODE_ENV === 'development') { skipAuth() }so missing env defaults to secure.- Secrets in URL query strings:
?api_key=xxxis logged by every proxy, load balancer, CDN, and browser history. Secrets must travel in request headers or request bodies over TLS. - Rotation breaks the app: a secret cached as
const jwtSecret = process.env.JWT_SECRETat module import time means deploying a rotated secret requires a full process restart. For high-rotation-frequency secrets, load at request time or use a reload signal handler. - Docker
ARGfor secrets:docker build --build-arg DB_PASSWORD=xxxstores the value in the image's layer history, visible viadocker history --no-trunc. Use runtime environment injection (docker run -eor orchestrator secrets mounts) instead.
Required output
## Env Config Hardening Report
### Critical findings
- [FILE:LINE or GIT-SHA] Description, impact, fix command or code change.
### High findings
- ...
### Medium findings
- ...
### Config validation status
- Library used: zod / joi / envalid / t3-env / none
- Validated at startup: yes/no
- Variables validated: list
### .env.example completeness
- Variables in example: N
- Variables consumed by app but missing from example: list
- Variables in example with real-looking values (risk): list
### Redaction status
- Logging library: X
- Fields redacted: list
- Unredacted sensitive fields found: list (file:line)
### Docker / CI injection
- Dockerfile ARG misuse for secrets: yes/no (files affected)
- CI workflow plaintext secrets: yes/no (files affected)
### Recommended immediate actions
1. [CRITICAL] Rotate any secret found in git history before making any other change.
2. ...
Safety
- Do not print actual secret values found — reference them by variable name and
file:lineonly, redacted to first/last 4 characters if any portion must be shown. - Do not modify
.envfiles, secrets manager configuration, or CI secret store entries. - Do not run
git filter-repoor history rewrite commands; document them for the user to run manually with explicit approval. - Do not commit changes to
.gitignoreor.env.examplewithout explicit user approval. - If a Critical live-secret finding appears (value committed to git history), surface it immediately and flag rotation as the blocking action before completing the rest of the review.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most project setup skills give in ~3.5k tokens
Counted across 999 of the 1,637 authors here whose files we hold, read 2026-08-07
- Ask one question at a timein 29 of 999, across 28 files
- Detect the package manager from lockfilesin 28 of 999, across 9 files
- Present findings to the userin 26 of 999, across 5 files
- Explore current repo statein 24 of 999, across 3 files
- Update the agent skills block in place if it existsin 24 of 999, across 3 files
- Install husky lint-staged and prettierin 23 of 999, across 4 files
- Create the lintstagedrc filein 22 of 999, across 3 files
- Commit all changed filesin 22 of 999, across 3 files
- Run lint-staged to verify it worksin 22 of 999, across 3 files
- Create the husky pre-commit filein 21 of 999, across 2 files
- Create a prettierrc file if missingin 21 of 999, across 2 files
- Initialize huskyin 21 of 999, across 2 files
Said here and by no other author read
- scan source code for hardcoded secrets
- ensure env example template is complete
- verify env secrets have no default fallbacks
- confirm logging middleware redacts secrets
- check secret rotation readiness
- verify dockerfiles use runtime secret injection
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.