How AI agents can use your passwords safely

Danielle Morrill's setup for giving Claude Code agents real credentials: 1Password as the source, a read-only service account per Mac, and the macOS keychain as the cache.

Every agent that does real work needs real credentials: a GitHub token, a hosting key, an App Store key. The lazy answer is to paste them into the chat, or to dump them into an environment file every process can read.

Danielle Morrill wrote up a better answer on 11th October 2026. Morrill is the co-founder and CEO of Groupthink, and before that co-founded Mattermark and was the first marketing hire at Twilio. Morrill runs 8 Claude Code sessions on a laptop that build features, merge pull requests, stage releases and verify deploys, mostly watched from a phone. The setup gets secrets to those agents without the agents ever seeing them in a chat, and without a person at the keyboard.

The problem

Morrill used to fetch a secret from 1Password and paste it into the agent's chat window, which the post calls "the worst place a secret can go". A pasted secret sits in the transcript, in logs and in the model provider's history. It also needs a human at the desk, and the goal was agents that keep working with nobody at the desk.

The setup: 3 layers

Layer Role Detail
1Password Source of truth All credentials in shared vaults, with a named field per machine if needed
1 read-only service account per Mac Gatekeeper Named after the machine, scoped to a few shared vaults, no private vault
macOS login keychain Cache Holds the service account token and the credentials scripts read often

3 design choices make it work:

  • Per-machine tokens. If a laptop is lost, revoking that 1 token cuts it off, and the other machines keep working.
  • Read-only scope. A leaked token can read a few vaults. It can't change or delete anything, and it can't see the owner's private vault.
  • No global export. Scripts read the keychain on each call. Nothing goes into .zshrc, because a global OP_SERVICE_ACCOUNT_TOKEN would also take over the interactive 1Password CLI, which still asks for Touch ID.

For business people

  • What it solves: AI agents need passwords to do real work. This setup gives them access without anyone typing or pasting a secret into a chat, and without a person at the computer.
  • The control model: 1 password manager stays the single source. Each computer gets its own read-only key to a small set of shared vaults. Cutting off a computer takes 1 click and doesn't affect the others.
  • Cost: 1Password includes service accounts on every plan, with lower usage limits on personal plans. No extra product to buy.
  • The trade-off, stated plainly: there is no approval prompt each time an agent uses a password. The control happens when you set the machine up and when you choose which vaults it can see. Any program running as you on that computer can read the cached passwords. For personal machines with read-only keys, Morrill accepts that. A company that needs a fingerprint for every access needs a different design.
  • Result: when Morrill moved the agents from a Mac mini to a laptop, every credential was already in place and verified. The agents worked on production within an hour, without anyone opening 1Password.

For technical people

Step 1: create the service account

Create 1 read-only service account per machine, in the 1Password web wizard or with the CLI:

op service-account create "macbook-pro" --vault "Agents:read_items"

1Password's rules, from the service account docs:

  • Permissions and vault access are immutable. To change them, create a new account and revoke the old one.
  • A service account can't access the built-in Personal, Private or Employee vault, or the default Shared vault. Put agent credentials in a dedicated vault.
  • Up to 100 service accounts. 1Password CLI 2.18.0 or later.

Step 2: put the token in the keychain

In a real terminal, not through the agent, store the token as a generic password:

security add-generic-password -U -a "$USER" -s OP_SERVICE_ACCOUNT_TOKEN -w '<token>'

Then confirm it works:

OP_SERVICE_ACCOUNT_TOKEN="$(security find-generic-password -a "$USER" -s OP_SERVICE_ACCOUNT_TOKEN -w)" op whoami

op whoami must report the user type SERVICE_ACCOUNT. The token now sits in your shell history too: clear that line, or start the command with a space if HIST_IGNORE_SPACE is on.

Step 3: seed each credential in 1 pipeline

This is the only step that touches a secret. The value goes from 1Password to the keychain inside 1 command, so nothing prints and the agent never sees it:

security add-generic-password -U -a "$USER" -s NAME_OF_SECRET \
  -w "$(op item get "Item name" --vault "Vault name" --fields "Field name" --reveal)"

Scripts then read the keychain at call time. In Python:

import getpass
import subprocess

def keychain_secret(name: str) -> str:
    """Read a generic password from the macOS login keychain."""
    return subprocess.run(
        ["security", "find-generic-password", "-a", getpass.getuser(), "-s", name, "-w"],
        capture_output=True, text=True, check=True,
    ).stdout.strip()

The cache also matters for rate limits. Each read through a service account counts:

Plan Reads per hour (per token) Calls per day (per account)
1Password Business 10,000 50,000
1Password Teams 1,000 5,000
1Password, Families 1,000 1,000

8 agents polling 1Password directly could burn through 1,000 calls a day on a personal plan. Reading from the keychain costs nothing. op service-account ratelimit shows the current usage.

Step 4: verify by use, never by the seed

The strongest rule in the post. A seed can fail and still report success, and length is not a check: a working token and a dead one were both 53 characters. Each credential must pass 1 real, read-only, authenticated call before anything depends on it: op whoami for 1Password, a deploy list for the hosting CLI, an account lookup for the error tracker.

What went wrong

3 seeding failures returned exit code 0 with no error:

Failure What actually got stored
-w with no value, in a non-terminal shell An empty string. Happened twice in 1 afternoon
-w "$(pbpaste)" Whatever was last copied, maybe the command text
A stale field in 1Password An old or wrong-account value the API rejected

The first one bites agents in particular: an agent's shell has no terminal, so the interactive prompt form of security add-generic-password silently stores nothing.

Step 5: write it down

A README per machine that lists the keychain entry names (never the values), how to re-seed after a rotation, and how to revoke the machine's token.

Agent rules

The agents run under 2 hard rules: never print a secret, and never touch production outside 2 named roles. The setup prompt for Claude Code in the post adds: never run curl | sh, and ask for approval before each change.

Limits

  • No per-use gate. Any process running as that user can read the cached credentials until they are removed or the token is revoked.
  • Reboots need a human. FileVault keeps the login keychain locked until someone logs in.
  • Private vault items still need a human at the machine, by design.

My take

This is the setup I should copy. My agents read API keys from a plain .env file today. That works, but every key is on disk in clear text, there's no per-machine revocation, and rotating a key means editing files by hand.

What I like most is the split of jobs. 1Password stays the master, the keychain is a fast local cache, and a read-only service account per machine limits the damage from any 1 machine. The rule I'll steal first, even before the migration, is verify by use: an empty or stale key that "saved fine" is exactly the kind of failure that costs an hour of debugging later.

The 1 gap for me: the Omarchy Mac mini runs Linux, see Omarchy, so it has no macOS keychain. There the same pattern needs the Secret Service keyring (secret-tool) instead of security.

Further reading

NicAI
Written by NicAI, Nic's AI assistant, for his personal knowledge base. Researched and drafted by the model, not hand-written by Nic. Verify anything you plan to act on.