From vibesubin
Manages full lifecycle of secrets and environment variables: decides placement (constant, .env, CI secret, env var), scaffolds .env.example/.gitignore, add/update/rotate/remove/migrate/audit/provision across envs. Language-agnostic.
npx claudepluginhub subinium/vibesubin --plugin vibesubinThis skill is limited to using the following tools:
Every project has two kinds of structural decisions. Some are low-stakes — which branch naming, which directory layout — and a mistake costs a little friction. Some are high-stakes — where a database password lives, whether `.env` is tracked, whether a production token is in a build-time variable — and a mistake costs an incident.
Analyzes environment variables in code, generates .env.example templates, validates configurations and types, documents variables with examples, and provides naming and security best practices.
Manages .env files, environment variables, configurations, and secrets across local, staging, production environments. Supports Docker, Kubernetes ConfigMaps/Secrets, AWS Secrets Manager, Vault, validation, and sync.
Guides secure secrets management using Vault, AWS Secrets Manager, Azure Key Vault, environment variables, rotation, scanning tools, and CI/CD security. For implementing storage, rotation, leak prevention, credentials review.
Share bugs, ideas, or general feedback.
Every project has two kinds of structural decisions. Some are low-stakes — which branch naming, which directory layout — and a mistake costs a little friction. Some are high-stakes — where a database password lives, whether .env is tracked, whether a production token is in a build-time variable — and a mistake costs an incident.
This skill owns the high-stakes slice: secrets, environment variables, and the gitignore that protects them. The low-stakes conventions (branches, directories, dep pinning, path portability) live in project-conventions. Splitting them this way means the operator can trigger the right depth of care for the right question.
The principle: the safest default is the one the operator doesn't have to invent. When they ask "where does my DB password go?", answer immediately, explain in one sentence, and offer to scaffold.
Every value in a project fits into exactly one of these four buckets. Use this decision tree to place anything.
Value
│
├── Does it NEVER change at runtime? (colors, labels, fixed limits)
│ └── YES → Constant in source code
│ Example: MAX_RETRIES = 3
│ Rule: Commit it to git, give it a descriptive name, UPPER_SNAKE_CASE
│
├── Does it change per environment (dev/staging/prod)?
│ │
│ ├── Is it a secret? (token, password, private key, DB URL)
│ │ │
│ │ ├── Local development environment?
│ │ │ └── `.env` file (gitignored) + committed `.env.example`
│ │ │
│ │ └── Production / CI environment?
│ │ └── CI / platform secret store (GitHub Secrets, Vercel env vars, Fly secrets, etc.)
│ │ Rule: Never echo secrets in logs. Reference by name only.
│ │
│ └── Non-secret but env-specific? (log level, feature flag, port)
│ └── Environment variable
│ Example: LOG_LEVEL=debug, PORT=8080
│
└── Is it user-configurable at runtime? (theme, locale, preferences)
└── Runtime config (database row, config file, API call)
Not this skill's concern. Use whatever pattern the app already has.
When the operator asks where a value should go, walk down this tree with them in one sentence: "This is a production database URL, so it belongs in your CI provider's secret store — not .env, because .env is local only."
Every hosting provider has a web UI for secrets, and every one of them also has a CLI that does the same thing faster and scripts cleanly. Prefer the CLI path — it's reproducible, diffable, and can live in a vm_setup.sh or a bootstrap script.
Do it the careful way: never paste a real secret directly into a command. Load it into a shell variable or a gitignored local file first:
# Prompt without echoing the value to the terminal
read -sr DATABASE_URL && export DATABASE_URL && echo
# Or load from a local file that is already gitignored
export DATABASE_URL="$(< .secrets/database_url)"
Then reference "$DATABASE_URL" in the CLI call. This keeps the real value out of docs, screenshots, and casual copy-paste examples.
Per-platform CLI cheatsheet — GitHub, Vercel, Netlify, Fly.io, Railway, Cloud Run, AWS — is in references/secrets-cli.md. Open it only when the operator is actually setting secrets for a specific host.
Core rules, regardless of host:
The deploy-workflow side of secrets (how CI consumes them) lives in the setup-ci skill. This skill answers where the value lives; setup-ci answers how the workflow uses it.
.env and .env.example — the ruleThe rule about .env is short:
.env — local only, never committed, contains real values..env.example — always committed, contains every key with either a safe default or a __REPLACE_ME__ placeholder, serves as the canonical list of required variables..env has DATABASE_URL and .env.example doesn't, someone will clone the repo and not know about that variable. When the skill adds a new env var, it updates both files.The ready-to-use starter .env.example with __REPLACE_ME__ placeholders, commented sections for required vs optional, and safe defaults for common knobs lives in templates/.env.example.template. Copy it, fill in the real keys, and you're done.
A .env.example with __REPLACE_ME__ placeholders only works if the application refuses to start when it sees them. Add a check in the application's boot path, before any real work. Ready-to-paste patterns for Python, Node, and Go live in references/startup-validation.md.
The core rule is the same across languages: read the value, check for empty or placeholder, abort boot with a clear error. Do not continue with a fallback — fallbacks hide the problem.
.env.example honestThe example file becomes useless the moment it drifts from the real .env. A ready-to-run drift check lives at scripts/check-env-drift.sh — wire it into a pre-commit hook, not CI. CI environments usually have no local .env, so the script would exit clean even when .env.example is out of date. Pre-commit catches it at the moment the operator adds a new variable.
Environment variables in frontend frameworks come in two flavors and the distinction causes the most common leak among non-developers.
NEXT_PUBLIC_* in Next.js, VITE_* in Vite, REACT_APP_* in CRA): baked into the client JavaScript bundle at build time. Anyone who views page source sees them. Never put a secret in a build-time variable. Safe uses: public API URLs, feature flags, non-secret configuration, analytics IDs.If a value is secret and also needs to be visible in the browser, it does not belong in env vars — it belongs behind an API route that reads the server-side secret and returns only the derived data the client actually needs.
.env files existMost frameworks load several .env files in a defined order. Later files override earlier ones. When debugging "why is this env var wrong?", always confirm which file the framework actually loaded — a value in .env can be silently overridden by .env.local.
.env → .env.<environment> → .env.local → .env.<environment>.local. The .local variants are gitignored by convention; .env.development / .env.production are committed only if they contain non-secret defaults.dotenv: one file by default. Use dotenv-flow or an explicit chain for multi-file support.python-dotenv: one file by default; use dotenv_values() merging for explicit multi-file chains..env.<environment> through dotenv-rails, with .env.local as an override..gitignore — the default-safe templateNon-developers routinely commit .env, SSH keys, build artifacts, and OS litter because they don't know to exclude them. The plugin ships a comprehensive default .gitignore at templates/.gitignore.template that blocks the usual suspects across Python, Node, Rust, Go, Java, OS / IDE, and local-only files.
Drop it in, trim the language sections the project doesn't use, and commit. If an existing .gitignore is missing secret-shaped entries (.env, *.pem, id_rsa), add them immediately — those are the ones that cause real incidents.
Secret-shaped entries to verify on every audit: .env, .env.* (except .env.example / .env.*.template), *.pem, *.key, id_rsa*, id_ed25519*, credentials*, secrets*, *.p12, *.pfx, service-account JSON keys (*-sa.json, *.credentials.json).
Placing a value in the right bucket is the first decision. Everything after is lifecycle: values get added, updated, rotated, removed, migrated between buckets, and audited across environments. This section is the opinionated playbook for each operation. Full per-step detail and edge cases live in references/lifecycle-workflows.md.
Use the four-bucket tree to decide where it goes, then run this checklist:
.env + .env.example, CI/platform secret store, or environment variable)..env, add the same key to .env.example with __REPLACE_ME__ as the value.references/startup-validation.md.CLAUDE.md. Hand off to write-for-ai if the table is stale or missing.The trap is forgetting one environment. A password changed in local .env but not in GitHub Secrets breaks the next deploy. Update in this order:
.env first (fastest feedback).gh secret list, vercel env ls, fly secrets list, etc. Don't assume one store is the only one.The most important lifecycle operation and the one most frequently skipped. Triggers: a collaborator leaves, a laptop is lost, a secret appears in logs, a scheduled rotation window, or a suspected leak. Incident rotation is the urgent path — scheduled rotation is the preventive one. Incident rotations hand off to audit-security first to assess blast radius.
Zero-downtime rotation (when the provider supports dual-valid credentials — most API keys, some JWT signing setups, IAM access keys):
ROTATION_LOG.md at the repo root (date, key name, trigger, actor).Downtime rotation (when dual-valid is not supported — database passwords, single-value signing secrets):
Provider-specific rotation recipes (AWS IAM, GCP service account keys, Stripe, OpenAI, database users, JWT signing keys, OAuth client secrets) live in references/secret-rotation.md. Always prefer OIDC over rotatable secrets when the platform supports it — a credential that doesn't exist cannot be rotated or leaked.
The trap is leaving orphan secrets in CI that nobody owns. Steps:
"DATABASE_" + "URL"), template rendering, YAML/TOML/JSON config files, shell scripts, Dockerfiles, CI workflow files.fight-repo-rot to confirm, then to refactor-verify to delete) or legitimate usage (the value is not removable yet; stop)..env and .env.example simultaneously so the drift check stays green.Triggers: a compile-time constant needs to vary per environment (source constant → env var), a local-only value becomes a shared secret (.env → CI secret store), a feature flag graduates from env var to a runtime config service. Steps:
refactor-verify for symbol-level validation that no caller was missed..env vs .env.example is the minimum check. The full audit compares every environment: local .env, staging, production, and each CI / platform secret store. Drift modes to detect:
DATABASE_URL is a Postgres URL in prod but a SQLite path in staging. Runtime type mismatch.__REPLACE_ME__ still present in staging or production.The skill produces a table: key, value-shape fingerprint per environment (length, prefix, suffix — never the real value), drift verdict. Never print real secret values — fingerprints are the primitive for diffing secrets safely.
Triggers: adding staging, spinning up a preview environment, onboarding a new deploy target, handing off a region. Steps:
.env.example as the canonical required-keys list.scripts/ so the operator can re-run or inspect it.CLAUDE.md via write-for-ai: name, purpose, URL, who owns it, what it's safe to break.Don't dump the entire skill. Answer the specific question in one paragraph and offer a concrete next step:
Operator: "Where should my DB password go?"
You: "In .env for local dev, in your CI provider's secret store for production. Don't put it in source code or in .env.example. I can set up .env.example with DATABASE_URL=__REPLACE_ME__ as a placeholder now — want me to?"
Operator: "Is my .env safe?"
You: "Checking. .env is gitignored ✅. .env.example has 8 keys, real .env has 10 — STRIPE_KEY and SENTRY_DSN are missing from the example. Want me to add them as __REPLACE_ME__ placeholders?"
.env. Ever. If you see .env tracked in git, flag it immediately and hand off to audit-security for remediation..env.example. That file is committable; real secrets go in .env.project-conventions.When invoked from /vibesubin (the umbrella skill's parallel sweep), this skill runs in read-only audit mode. Do not scaffold .env.example, do not edit .gitignore, do not touch any config files. Do not run lifecycle workflows (no rotate, no remove, no migrate, no provision).
Instead, produce a findings-only report:
.env?.env.example drift: does every key in .env have a matching entry in .env.example? Any __REPLACE_ME__ placeholders still live in a real environment?.gitignore coverage: is .env actually ignored? Any *.pem, id_rsa, credentials* files missing?git ls-files? (These are incident-class — flag immediately and hand off to audit-security.).gitignore entries, or other incident-class findings./manage-secrets-env will apply the scaffolds or run the lifecycle workflow when invoked directly.The operator reviews the sweep report and, if they want the fixes applied, invokes /manage-secrets-env directly — which then runs the full opinionated procedure.
How to tell: the task context from the umbrella will include a sweep=read-only marker or an explicit "produce findings only, do not edit" instruction. Obey it. If the operator invokes this skill by name, the full procedure applies and editing is expected.
When the task context contains the tone=harsh marker (usually set by the /vibesubin harsh umbrella invocation, but can also come from direct requests like "don't sugarcoat" / "brutal review" / "매운 맛" / "厳しめ"), switch output rules:
.env is tracked in git or a secret is in source, that's the first line of the report with file and line.src/config.ts:14 and is readable by anyone who clones this repo", not "potential secret exposure in config.ts".git ls-files returns a secret-shaped file, the first line is "Stop. Rotate the credential now. Handing off to audit-security incident runbook." No preamble.STRIPE_KEY is missing from .env.example". Harsh mode says "add STRIPE_KEY=__REPLACE_ME__ to .env.example now — the next collaborator will clone a broken app.".env is untracked and .gitignore covers *.pem", not "a couple of hygiene items to watch".Harsh mode does not invent findings, fabricate severities, or become rude. Every harsh statement must still cite the same file, line, or git ls-files match the balanced version would cite. The change is framing, not substance.
audit-security immediately for blast-radius assessment, then run the Rotate a secret workflow above as the incident path.audit-security for severity, refactor-verify for the upgrade diff.refactor-verify for the 1:1 preservation and symbol-level proof.CLAUDE.md env-var table → write-for-ai with the key, one-line purpose, and default or placeholder value.fight-repo-rot to confirm, then refactor-verify to delete safely.project-conventions, which owns those lower-stakes conventions.references/secrets-cli.md — per-platform CLI walkthroughs (GitHub, Vercel, Netlify, Fly, Railway, Cloud Run, AWS)references/startup-validation.md — Python / Node / Go patterns for refusing to start on unfilled __REPLACE_ME__references/secret-rotation.md — provider-specific rotation recipes (AWS IAM, GCP SA keys, Stripe, OpenAI, DB users, JWT signing, OAuth)references/lifecycle-workflows.md — deep dive on add / update / rotate / remove / migrate / audit / provision with edge casestemplates/.env.example.template — starter .env.example with __REPLACE_ME__ placeholderstemplates/.gitignore.template — comprehensive default .gitignorescripts/check-env-drift.sh — pre-commit-friendly script that fails if .env and .env.example key sets diverge