Managing Secrets with Chezmoi and 1Password Service Accounts
Author: Nicolas Rouanne
Date: May 7, 2026
I use chezmoi to manage my dotfiles across machines, with secrets stored in 1Password. Until recently, everything ran through my personal 1Password account. That worked fine for me alone, but once I started giving AI agents (Claude Code) access to the op CLI, I realized they could see every vault in my account. Not great.
Here's how I moved to 1Password Service Accounts to keep agents isolated to only the secrets they need.
The setup before
My dotfiles repo contains chezmoi templates. The main one is dot_zshrc.tmpl, which generates ~/.zshrc with secrets injected at apply time. The flow was:
- Chezmoi reads secrets from 1Password using
onepasswordRead - Secrets get baked into the generated
~/.zshrcas environment variables - MCP servers (Notion, Slack, Toggl) read those env vars at startup
The template looked like this:
export NOTION_TOKEN_WORK="{{ onepasswordRead "op://Qraft/chezmoi_notion_work/api_key" "my.1password.eu" }}"
export SLACK_QRAFT_USER_TOKEN="{{ onepasswordRead "op://Qraft/chezmoi_slack-qraft/user_token" "my.1password.eu" }}"
export TOGGL_API_TOKEN="{{ onepasswordRead "op://Qraft/chezmoi_toggl/api_token" "my.1password.eu" }}"All secrets lived in my personal Qraft vault. Chezmoi connected to my.1password.eu using my personal session, which required biometric auth (Touch ID).
The problem: when Claude Code ran op commands, it used that same session and could access all my vaults.
The solution: a dedicated vault and service account
The fix has three parts:
1. Create a dedicated vault
I created a vault called AI Agents in 1Password and copied the relevant items into it:
chezmoi_notion_personal/chezmoi_notion_work(Notion API keys)chezmoi_slack-qraft/chezmoi_slack-episto(Slack tokens)chezmoi_toggl(Toggl API token)chezmoi_langfuse(Langfuse keys)
This is a flat copy. 1Password doesn't support vault hierarchies, so naming conventions do the work.
2. Create a service account
In the 1Password web UI: Settings > Developer > Service Accounts. I created sa-claude-code with read/write access to the AI Agents vault only.
The service account gives you a token (ops_...) that authenticates non-interactively. No Touch ID, no MFA, no personal session. But it can only see the vaults you assign to it.
3. Update chezmoi to use the service account
Two files changed.
.chezmoi.toml.tmpl — switch from personal account to service mode:
sourceDir = "{{ .chezmoi.homeDir }}/dev/dotfiles"
\[onepassword\]
mode = "service"When mode = "service", chezmoi uses the OP_SERVICE_ACCOUNT_TOKEN environment variable instead of an interactive session.
dot_zshrc.tmpl — load the token from macOS Keychain and point to the new vault:
# Load SA token from Keychain (no interactive prompt)
export OP_SERVICE_ACCOUNT_TOKEN=$(security find-generic-password -a "sa-claude-code" -s "1password-service-account" -w)
# Secrets now come from the AI Agents vault
export NOTION_TOKEN_WORK="{{ onepasswordRead "op://AI Agents/chezmoi_notion_work/api_key" }}"
export SLACK_QRAFT_USER_TOKEN="{{ onepasswordRead "op://AI Agents/chezmoi_slack-qraft/user_token" }}"The account parameter ("my.1password.eu") is no longer needed — service mode doesn't use it.
The bootstrap problem
There's one chicken-and-egg issue: chezmoi needs OP_SERVICE_ACCOUNT_TOKEN to generate the zshrc, but the zshrc is what loads that token from the Keychain.
For a fresh machine, you need to manually set the token first:
# Store the token in Keychain
security add-generic-password -a "sa-claude-code" -s "1password-service-account" -w "ops_..."
# Export it for the initial run
export OP_SERVICE_ACCOUNT_TOKEN=$(security find-generic-password -a "sa-claude-code" -s "1password-service-account" -w)
# Then init chezmoi
chezmoi init --apply https://github.com/your/dotfiles.gitAfter that first apply, every new shell loads the token automatically.
What I'd note
What works well:
- Agents are fully isolated —
op vault listwith the SA token shows onlyAI Agents - No interactive prompts —
chezmoi applyruns silently - My personal 1Password session stays independent for direct use
- The service account token itself doesn't live in version control (it's in the macOS Keychain)
Limitations:
- 1Password has no vault hierarchy. You can't do
Agents/Qraft,Agents/Episto. It's a flat list, so naming conventions matter. - Service accounts can't be created via the CLI — you have to use the web UI
- The Keychain approach on macOS doesn't require biometric auth for
security find-generic-password -w, which is both the point and the tradeoff
Practical takeaway
If you're giving AI coding agents access to op, don't let them use your personal session. Create a service account, scope it to a dedicated vault, and store the token somewhere that doesn't require interactive auth. It takes about 15 minutes and the isolation is worth it.