Migrate from .env to Vault
Move your team from flat .env files to encrypted, audited, scoped secret storage — using the CLI for a one-command import.
Never commit .env files
.env files containing real credentials should never be committed to source control — even to private repositories. Secrets in git history are hard to remove and can leak via forks, CI logs, or repository access grants. If this has happened, rotate all affected secrets immediately after completing this migration.
- 1
Install the CLI
Shellnpm install -g @agentsecretstore/cliAlso available via Homebrew and direct download — see the CLI Quickstart.
- 2
Create your account and get an API key
Sign up at agentsecretstore.com/signup (free, no credit card required), create an agent, then expose its key to the CLI:
Shell# Create an agent in the dashboard, then export its key. export ASS_AGENT_KEY="ass_xxxxxxxxxxxxxxxxxxxxxxxx" ass status # status ● Operational # logged in yes - 3
Import your .env file
Use
ass env importto bulk-create secrets from your .env. Always dry-run first:Shell# Dry run first — preview what will be created ass env import .env --namespace production --dry-run # GEMINI_API_KEY gem•••••••• # STRIPE_SECRET_KEY sk_l•••••••• # DATABASE_URL post•••••••• # SLACK_BOT_TOKEN xoxb•••••••• # Dry run — no changes made # Looks good — run for real ass env import .env --namespace production # ✓ Imported: production/GEMINI_API_KEY # ✓ Imported: production/STRIPE_SECRET_KEY # ✓ Imported: production/DATABASE_URL # ✓ Imported: production/SLACK_BOT_TOKEN # ✓ Imported 4 secrets to production - 4
Verify the import
Confirm every secret was created correctly:
Shellass secrets list production # production/GEMINI_API_KEY custom standard just now # production/STRIPE_SECRET_KEY custom standard just now # production/DATABASE_URL custom standard just now # production/SLACK_BOT_TOKEN custom standard just now - 5
Update your code
Replace
os.environ/process.envreads with vault lookups:Python — before
credentials.py# BEFORE — reading from environment / .env import os gemini_key = os.environ["GEMINI_API_KEY"] stripe_key = os.environ["STRIPE_SECRET_KEY"] db_url = os.environ["DATABASE_URL"] slack_token = os.environ["SLACK_BOT_TOKEN"]Python — after
credentials.py# AFTER — reading from Agent Secret Store vault import asyncio import os from agentsecretstore import AgentVault async def load_credentials() -> dict[str, str]: async with AgentVault(agent_key=os.environ["ASS_AGENT_KEY"]) as vault: # Fetch all credentials in parallel gemini, stripe, db, slack = await asyncio.gather( vault.get_secret("production/GEMINI_API_KEY"), vault.get_secret("production/STRIPE_SECRET_KEY"), vault.get_secret("production/DATABASE_URL"), vault.get_secret("production/SLACK_BOT_TOKEN"), ) return { "gemini_key": gemini, "stripe_key": stripe, "db_url": db, "slack_token": slack, } # One-liner helper for simple scripts async def get(path: str) -> str: async with AgentVault(agent_key=os.environ["ASS_AGENT_KEY"]) as vault: return await vault.get_secret(path)TypeScript — before
credentials.ts// BEFORE — reading from process.env (requires dotenv) import 'dotenv/config'; const geminiKey = process.env.GEMINI_API_KEY!; const stripeKey = process.env.STRIPE_SECRET_KEY!; const dbUrl = process.env.DATABASE_URL!; const slackToken = process.env.SLACK_BOT_TOKEN!;TypeScript — after
credentials.ts// AFTER — reading from Agent Secret Store vault import { AgentVault } from '@agentsecretstore/sdk'; const vault = new AgentVault({ agentKey: process.env.ASS_AGENT_KEY! }); async function loadCredentials() { const [gemini, stripe, db, slack] = await Promise.all([ vault.getSecret('production/GEMINI_API_KEY'), vault.getSecret('production/STRIPE_SECRET_KEY'), vault.getSecret('production/DATABASE_URL'), vault.getSecret('production/SLACK_BOT_TOKEN'), ]); return { geminiKey: gemini, stripeKey: stripe, dbUrl: db, slackToken: slack, }; } - 6
Remove .env from your codebase
Once your code is updated and tested against the vault, stop tracking .env files:
Shell# Stop tracking .env git rm --cached .env # Ensure .env is in .gitignore echo ".env" >> .gitignore echo ".env.*" >> .gitignore echo "!.env.example" >> .gitignore git add .gitignore git commit -m "chore: remove .env from tracking, add to .gitignore"If .env was previously committed to git history:
Shell# If .env was previously committed — remove from git history # Option 1: git-filter-repo (recommended) pip install git-filter-repo git filter-repo --path .env --invert-paths # Option 2: BFG Repo Cleaner java -jar bfg.jar --delete-files .env my-repo.git # After removing from history — force push and rotate all secrets! git push --force-with-lease origin mainRotate after purging history
If credentials were in git history, assume they have been compromised. Rotate all affected secrets immediately after removing them from history.
Replace your .env with a safe
.env.exampletemplate:.env.example# .env.example — safe to commit, no real values GEMINI_API_KEY=gemini-api-key-example STRIPE_SECRET_KEY=sk_live_... DATABASE_URL=postgresql://user:pass@host:5432/db SLACK_BOT_TOKEN=xoxb-... # Real values are in Agent Secret Store. Run: # ass env export production --output .env.local - 7
Register each service as a separate agent
Create one agent per service with the minimum required scopes. This ensures a compromised service can only access its own secrets:
Shell# Register one agent per service with minimum required access. ass agents create # Agent name: inference-service # Description: Gemini inference pipeline # Allowed namespaces: production # Allowed scopes: secrets:read:* ass agents create # Agent name: payment-service # Description: Stripe payment processor # Allowed namespaces: production # Allowed scopes: secrets:read:*Least-privilege principle
Your inference service doesn't need your Stripe key. Your payment service doesn't need your Gemini key. Scoped agents enforce this at the vault level — not just convention.