.env exports to user env vars daily — MCPs read from system env
.env → user env vars, refreshed daily
Rule
.env.enc (sops+age encrypted) → .env (gitignored) → user-scope Windows environment variables → MCP servers read at launch. Single source of truth: .env.
No secrets committed. No secrets in .mcp.json. No per-agent auth file duplication.
Mechanic
- Age key — imported once per machine from Bitwarden (
~/.age/keys.txtorSOPS_AGE_KEYenv var). .env.enc— committed to workspace, sops+age encrypted.sync-env-to-system-env.ps1— decrypts.env.enc→.env, reads everyKEY=VALUE, calls[Environment]::SetEnvironmentVariable(key, value, 'User').- Windows Scheduled Task
Oriz-SyncEnv— runs the script at logon + daily 09:00. Registered byscripts/register-scheduled-tasks.ps1. - MCP servers — reference secrets via
${env:VAR_NAME}in their config (e.g..mcp.json,~/.config/opencode/opencode.jsonc). - Per-agent auth (Anthropic key, OpenAI key) — same pattern; agent reads from env.
Why user-scope, not machine-scope
- User-scope: no admin needed, per-user isolation.
- Machine-scope: needs admin, shared across users on same machine (unnecessary).
- Both survive reboots; both refresh on logon.
Why refresh daily
- Age key rotation → refresh picks up new decrypt.
- New MCP added to
.env.enc→ auto-propagates within 24h. - Manual override:
pwsh scripts/sync-env-to-system-env.ps1for immediate effect.
Anti-patterns
- ❌ Committing
.env(gitignored, but never bypass) - ❌ Hardcoding secrets in
.mcp.json— always${env:VAR} - ❌ Per-agent duplicated auth files (
.opencode/auth.json,.kilocode/creds.json) — read from user env - ❌ Age key committed anywhere — Bitwarden-only per machine
Cross-refs
- [[decisions/agent-tooling/fleet-cc-opencode-kilo-2026-07-03]] — MCP-per-agent + env pattern
- [[rules/security/no-hardcoded-secrets]]
- [[services/business/secrets/sops-plus-doppler-hybrid]]
- Script:
scripts/sync-env-to-system-env.ps1 - Registration:
scripts/register-scheduled-tasks.ps1