Secrets & Environment Management
Pathrule3 Rules • 2 Memories • 1 Skill
A pattern bundle for handling API keys, tokens, and credentials safely across local development, CI, and production. It enforces .env hygiene, pushes teams toward a managed secret store with runtime injection and OIDC, and ships a review checklist so no credential ever lands in source control or a build log.
Suggested path map
Pathrule places each piece on the matching path, so your assistant only sees it where it belongs. This is the scoping you get on import; you can adjust it in your workspace.
Rules
3No live secret enters git history/roothighstrictPlaintext env files are gitignored before the first commit; only example or encrypted env files are tracked.
| 1 | A file containing a live credential must never be committed, and the ignore rules must exist before the first commit, not after the first leak. |
| 2 | |
| 3 | - Add `.env`, `.env.*`, and `.env.local` to `.gitignore`, then explicitly un-ignore the files you DO want tracked: `!.env.example` and, if you use dotenvx, `!.env.production` (the encrypted artifact). Keep `.env.keys` ignored always. |
| 4 | - Track only `.env.example` with placeholder values, or an encrypted file where the decryption key lives outside the repo. With dotenvx, `dotenvx encrypt` writes the `DOTENV_PUBLIC_KEY` plus ciphertext into the committed `.env` and the matching `DOTENV_PRIVATE_KEY` into the never-committed `.env.keys`; with sops, the data key is held by KMS/age, not the repo. |
| 5 | - Run gitleaks at the gate, not just in CI. As of v8.19.0 the `protect` and `detect` subcommands are deprecated; the current pre-commit invocation is `gitleaks git --pre-commit --staged --redact --no-banner` (wire it into a Lefthook or Husky `pre-commit` hook), and CI runs `gitleaks dir .` (or `gitleaks git` for history). |
| 6 | - Encryption is not a license to relax this rule: an attacker with the decryption key gets everything, so the private key is itself a top-tier secret and never lives in the repo. |
Server secrets never cross into the client bundle/srchighadvisoryRead secrets only in server code; only intentionally public-prefixed vars ship to the browser.
| 1 | Anything bundled for the browser is world-readable, so a server secret referenced from a client-reachable module is a public secret the moment you build. |
| 2 | |
| 3 | - Read secrets only in server code (route handlers, server actions, API layers, background jobs). A secret imported into a React/Vue/Svelte component that renders client-side WILL be inlined into the bundle by the bundler, even if the code path looks server-only. |
| 4 | - Treat any value behind a public prefix (`NEXT_PUBLIC_`, `VITE_`, `PUBLIC_`, `EXPO_PUBLIC_`) as published. Never put a real secret behind one to "make it work" on the client. |
| 5 | - Validate the whole environment once at startup with a typed schema (for example `zod` or `@t3-oss/env-nextjs`) that separates server vars from client vars, so a missing or misnamed secret fails the build instead of surfacing as a runtime `undefined`. |
| 6 | - Never log a secret, return it in a response body, or send it to an analytics/error tracker. Redact before it reaches any sink. |
CI authenticates via OIDC, not stored static keys/.github/workflowshighadvisoryPipelines federate to a short-lived cloud role instead of storing long-lived access keys as secrets.
| 1 | A long-lived cloud key stored as a CI secret is the highest-value, lowest-rotation credential most teams hold. Federate instead so the credential is minted per run and expires on its own. |
| 2 | |
| 3 | - Grant the job `permissions: id-token: write` and exchange GitHub's OIDC JWT for a role. For AWS use `aws-actions/configure-aws-credentials` with `role-to-assume` and no `aws-access-key-id`/`aws-secret-access-key`. GCP and Azure have equivalent workload-identity federation actions. |
| 4 | - The minted credential is short-lived (AWS STS defaults to up to 1 hour) and scoped to exactly the IAM role's trust policy. There is no static key in the environment to leak or rotate. |
| 5 | - Lock the IAM trust policy to your `repo:org/name`, and constrain on `ref`/`environment` claims so only the intended branch or protected environment can assume the role. Note the immutable `sub` subject-claim rollout for new repositories landing June 18, 2026 and pin your trust condition to claims that survive it. |
| 6 | - Use platform-native OIDC for app-to-cloud calls too (for example Vercel exposes `VERCEL_OIDC_TOKEN`, TTL ~60 min, cached ~45 min) so deployed functions never carry a static cloud key either. |
Memories
2Secret storage tiers: local, CI, production/rootWhere secrets live and how they reach the app at each stage, with no plaintext handed around.
| 1 | Secrets flow through three tiers, each with its own source of truth, so no plaintext credential is ever shared by hand or pasted into chat. |
| 2 | |
| 3 | - Local: developers keep an uncommitted `.env.local`, or pull from the team store with a CLI (`doppler run --`, `infisical run --`, `vault read`, or `vercel env pull`). New keys are shared through the store, never over Slack/email. With dotenvx, devs run `dotenvx run -- <cmd>` which decrypts the committed `.env` using the `.env.keys` private key. |
| 4 | - CI: the pipeline authenticates with GitHub OIDC to assume a short-lived cloud role rather than holding static keys (see /.github/workflows). dotenvx users keep an environment-scoped `DOTENV_PRIVATE_KEY_CI` in the CI secret store as the single bootstrap secret. |
| 5 | - Production: a managed store (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, or the platform env UI) injects values at runtime; or set `DOTENV_PRIVATE_KEY_PRODUCTION` ahead of `dotenvx run --` so the encrypted `.env` is decrypted in memory at boot. Prefer Vault dynamic secrets, which mint per-request credentials with a built-in TTL and auto-revoke, so there is effectively nothing to rotate. |
| 6 | |
| 7 | See /src for the runtime read and client-bundle rules, and /.github/workflows for the OIDC pipeline wiring. |
Rotation, least privilege, and leaked-secret response/rootLifetime and scoping defaults for every credential, plus the rotate-then-scrub runbook when one leaks.
| 1 | Every credential is scoped narrowly and has a known expiry, so a leak has a small blast radius and a short window. When one does leak, order of operations matters. |
| 2 | |
| 3 | Defaults for every new credential: |
| 4 | - Issue a distinct key per service or consumer; never share one key across apps, so revocation is surgical instead of a fleet-wide outage. |
| 5 | - Grant least privilege: scope to the exact resources and actions needed, read-only where possible. |
| 6 | - Prefer dynamic or OIDC short-lived credentials so rotation is automatic. For unavoidable static keys, schedule rotation (for example AWS Secrets Manager rotation for RDS) and alert before expiry. |
| 7 | - Keep audit logging on at the store so every access (who, when, what) is traceable, and revoke immediately on offboarding. |
| 8 | |
| 9 | When a secret is exposed (committed, logged, or pasted), in this order: |
| 10 | 1. Rotate first. The secret is compromised the instant it leaves the trust boundary; assume it is already scraped. Revoke the old credential at the provider and issue a new one before anything else. |
| 11 | 2. Then scrub history (`git filter-repo` or BFG) and force-push, and purge it from any build logs, artifacts, and error trackers. Deleting the commit or rewriting history alone does NOT uncompromise the secret, which is why rotation comes first. |
| 12 | 3. Check the provider's audit log for use during the exposure window, and rotate any credential that was derivable from the leaked one. |
Skills
1secrets-env-management-review/rootPre-merge checklist to confirm no secret leaks and env handling is sound.
| 1 | --- |
| 2 | name: secrets-env-management-review |
| 3 | description: Review checklist for any change that touches secrets, env vars, CI credentials, or config. Run before merging to confirm no credential leaks into git, logs, or the client bundle. |
| 4 | --- |
| 5 | |
| 6 | # Secrets & environment management review |
| 7 | |
| 8 | - [ ] No plaintext secret is added to a tracked file; `.env`, `.env.*`, and `.env.local` are in `.gitignore`, with explicit `!.env.example` (and `!.env.production` only if it is the encrypted dotenvx artifact). |
| 9 | - [ ] `.env.keys` (or any sops/age private key) is gitignored and not present in the diff. |
| 10 | - [ ] Only `.env.example` (placeholders) or an encrypted file (dotenvx/sops, key stored outside the repo) is committed; no decryption key is in the repo. |
| 11 | - [ ] gitleaks runs at the pre-commit gate via `gitleaks git --pre-commit --staged --redact` and in CI via `gitleaks dir .`; the diff passes a secret scan. (Do not use the deprecated `protect`/`detect` subcommands.) |
| 12 | - [ ] Secrets are read server-side only; nothing sensitive is referenced from client-reachable modules or exposed via a public prefix (`NEXT_PUBLIC_`, `VITE_`, `PUBLIC_`, `EXPO_PUBLIC_`). |
| 13 | - [ ] Env is validated at startup with a typed schema that separates server and client vars, so a missing or misnamed var fails the build. |
| 14 | - [ ] No secret value is logged, returned in a response, or sent to analytics/error tracking. |
| 15 | - [ ] CI uses `id-token: write` + OIDC role assumption (no `aws-access-key-id` etc. stored as secrets); the IAM trust policy is locked to the repo and ref/environment. |
| 16 | - [ ] Production reads secrets from a managed store or decrypts at runtime, not from a baked-in build artifact. |
| 17 | - [ ] Each new credential is least-privilege, distinct per consumer, and has a rotation policy or short TTL. |
| 18 | - [ ] Any previously exposed secret was rotated FIRST, then scrubbed from history and logs (not just deleted). |
Why this pattern
Secrets keep leaking into commits, build logs, and client bundles because there is no enforced convention for where credentials live or how they reach runtime.
Built for Backend and platform teams shipping services with API keys, database credentials, and CI/CD pipelines..
Keeps your assistant from:
- Committing a real .env file or hardcoded API key to git history
- Leaking server-side secrets into a client bundle via the wrong env prefix
- Long-lived static cloud keys sitting in CI with no rotation or expiry
- License
- Apache-2.0
- Version
- 1.0.0
- Updated
- 2026-06-09