vercel-sensitive-envs is a local CLI for teams that need to rotate Vercel environment variables safely and deliberately.
It is designed for a common incident-response workflow:
- inventory current env vars
- prepare a replacement manifest
- rotate
productionfirst - rotate
previewsecond - recreate those vars as
sensitive
The tool is intentionally conservative. It defaults to plan-only mode, preserves existing scope metadata where possible, and refuses to rotate if your manifest still contains Vercel ciphertext blobs or generated placeholders.
Vercel supports sensitive environment variables, but migrating existing values into that format is awkward in practice:
- sensitive vars cannot simply be toggled in place
- some existing vars target both
productionandpreview - bulk export APIs do not consistently return plaintext for all non-sensitive values
- incident response often requires careful sequencing, especially if production must be updated before preview
This CLI automates the parts that are easy to get wrong.
- Supports project-scoped env vars and shared team env vars
- Rotates
productionbeforepreview - Safely splits multi-target env vars during rotation
- Exports a JSON manifest you can review and edit before making changes
- Preserves comments where possible
- Detects Vercel ciphertext blobs and converts unreadable exports into explicit placeholders
- Refuses to perform writes when manifest values are clearly unresolved
- Node.js
>=18 - A Vercel token with access to the relevant team or project
Create a token here:
Clone the repo and install:
npm installFor local CLI use without publishing:
npm link
vercel-sensitive-envs --helpVERCEL_TOKEN=... vercel-sensitive-envs \
--scope project \
--project your-project-name \
--team-slug your-team-slug \
--dump-non-sensitive-manifest \
--output ./vercel-sensitive-rotation.generated.jsonIf Vercel refuses to return plaintext for a value, the generated file will include:
- a
REPLACE_WITH_NEW_*placeholder inentries - a separate
.unresolved.jsonreport next to the exported manifest describing which keys still need manual values
Edit the generated manifest and replace every placeholder with the real new value you want Vercel to store.
This matters: Vercel stores exactly what you send. A placeholder or ciphertext blob is not a valid replacement secret.
VERCEL_TOKEN=... vercel-sensitive-envs \
--scope project \
--project your-project-name \
--team-slug your-team-slug \
--manifest ./vercel-sensitive-rotation.generated.jsonVERCEL_TOKEN=... vercel-sensitive-envs \
--scope project \
--project your-project-name \
--team-slug your-team-slug \
--manifest ./vercel-sensitive-rotation.generated.json \
--yesAfter rotating environment variables, you will usually want to trigger fresh deployments in Vercel so new builds and runtime instances pick up the updated values.
In practice that often means:
- redeploying production after the production rotation completes
- redeploying preview after the preview rotation completes
If your application reads the secret only at runtime, a restart may be enough. If the value is used at build time, you should expect a full redeploy to be necessary.
{
"entries": [
{
"key": "PRIMARY_SERVICE_TOKEN",
"production": "new-production-value",
"preview": "new-preview-value"
},
{
"key": "NOTIFICATION_PROVIDER_SECRET",
"all": "same-value-for-production-and-preview"
},
{
"key": "INTERNAL_SIGNING_SECRET",
"production": "prod-only-value"
}
]
}all means the same value should be used for both production and preview.
Show help:
npm run helpExport a manifest:
npm run export -- \
--scope project \
--project your-project-name \
--team-slug your-team-slug \
--output ./vercel-sensitive-rotation.generated.jsonRun a plan-only rotation:
npm run rotate -- \
--scope project \
--project your-project-name \
--team-slug your-team-slug \
--manifest ./vercel-sensitive-rotation.generated.jsonUse --scope project with:
--project <project-id-or-name>--team-id <team-id>or--team-slug <team-slug>when needed
Use --scope shared with:
--team-id <team-id>or--team-slug <team-slug>- optionally
--project <project-id>to filter shared env vars associated with a specific project
sensitivevars are intentionally unreadable from the API.- Some Vercel
encryptedvars do not come back as plaintext even when using bulk export withdecrypt=true. - Even the per-ID decrypted endpoint may still refuse to return plaintext for some
encryptedvars. - Shared env var export is more limited than project env export.
- Branch-scoped preview vars are skipped by default. Pass
--allow-git-branch-varsif you explicitly want them.
Because of those API limitations, this tool treats unreadable values as unresolved inputs, not reusable outputs.
This tool is intended to be run locally and is relatively safe for that use case, with the usual secret-handling precautions:
- It runs on your machine and talks directly to the Vercel API.
- It does not send secrets to any third-party service beyond Vercel.
- It does not persist your Vercel token unless you choose to do that yourself.
- Generated manifest files can contain real plaintext secrets and should be treated as sensitive files.
Recommended practices:
- use
export VERCEL_TOKEN=...in your current shell instead of storing the token in a tracked file - never commit generated manifests
- delete manifest files once rotation is complete
- use a temporary working directory if you want stricter local hygiene
For each targeted key:
- the tool inspects current scope and target metadata
- if a single env var currently targets both
productionandpreview, it narrows the old record first - it recreates the current phase as a new
sensitiveenv var - it processes
productionbeforepreview - it verifies that the final records for the rotated targets are
sensitive
That sequencing reduces the chance of clobbering the wrong target during a migration.
Run the standalone CLI directly:
node ./bin/vercel-sensitive-envs.mjs --help