Wire Your Secrets Manager.
The hands-on build behind the secrets primer. Wire Infisical by default, or use 1Password if you prefer it, so your scripts read credentials at runtime. The real value never sits on disk, never lands in git, never gets pasted into a conversation. Set this up once on day one and every later card builds on it. New here? Read the why first in Secrets Management, then come back.
- A dedicated agent project in Infisical (or a 1Password vault if you prefer it) that only holds the credentials your agents touch, separated from your personal passwords.
- Runtime references in your config instead of raw API keys. Secrets inject at launch through
infisical run(or resolve from anop://link on the 1Password path), so your settings file is safe to commit, share, or screenshot. - A locked .env bridge for the moments when an app expects environment variables. Gitignored, chmod 600, and verified invisible to git.
Build this with Claude.
Hand the guide off to Claude in one paste. Claude reads the source guide, then walks you through each step, pausing for your confirmation between steps.
Before the build: the why.
This card is the hands-on setup. If you have not read Secrets Management yet, start there. It covers the one rule (never paste a real key into a chat), the three storage paths, and why Infisical is the recommended setup. This card assumes you have picked a path and are ready to wire it. You do this once, and every later workshop build (MCPs, overnight cron, the Morning Brief) inherits the posture you set up here.
Before you start.
Three things specific to this build
- A secret manager you actually use. Infisical is the recommended setup. The installer already signed you in. 1Password works the same way if you prefer it.
- One fresh API key to test the flow end to end. Brave Search is a good one (free tier, takes 2 minutes to generate at
brave.com/search/api). You can also use any key you already have on your computer that needs a real home. - The workshop installer done. That put the
infisicalCLI on your computer and signed you into Infisical Cloud. Verify withinfisical --versionandinfisical whoamibefore starting. On the 1Password path, install theopCLI and confirm the desktop app integration withop whoami.
The build.
Six steps. About 45 minutes. The pattern is: create a dedicated agent project, scaffold one secret with the placeholder pattern, wire your config to read the secret at runtime, verify the agent can resolve it without printing the full value, and smoke-test the end-to-end flow. Each step leads with the Infisical path (the recommended setup) and then gives the 1Password equivalent. Follow whichever one matches your setup. The optional Step 05 covers the .env bridge when you need it.
The agent does the scaffolding for you. The clicks you do yourself are the ones that touch your real keys: typing the value into your Infisical dashboard or 1Password app, clicking through the API provider's dashboard, copying a reference. Everything else is Claude staging files and walking you through the moves.
Create a dedicated agent vault or project
5 minMake a scoped store that holds only the credentials your agents touch, separate from the rest of your passwords. The point is scoping: a service token you generate later can read this store only, not your bank logins or your personal mail.
- Infisical (recommended): At
app.infisical.com, click New Project and name it something obvious likeAgent Secrets. Each project comes withdev,staging, andprodenvironments. You'll usedev. Then, in your workshop project directory, runinfisical initto link the folder to the project. That writes a.infisical.jsonfile (no secrets in it, just the project ID and default environment, safe to commit). - 1Password: Click your account in the sidebar, then New Vault. Name it
Agent Vault. Save. A service token you generate later reads this vault only.
This project (or vault) is where every workshop credential lands from this point forward. Personal passwords stay somewhere the agent never sees.
I've created an Infisical project called Agent Secrets at app.infisical.com. From my current workshop project directory, run infisical init and walk me through linking it to that project with dev as the default environment. Confirm a .infisical.json file was created and that it contains no secret values before we move on.
infisical login, pick Infisical Cloud (US Region), authorize in the browser, then retry. Using 1Password instead? Create a 1Password vault called Agent Vault, confirm the op CLI is installed and connected to the desktop app with op whoami, and enable 1Password CLI integration if needed. If you need to stall on choosing a secret manager, skip to Step 05 and use the .env bridge. You can migrate later.
Scaffold your first secret with PASTE_HERE
8 minThis is the move that protects you. Claude creates the entry in your secret manager with the literal text PASTE_HERE as the value, then you fill in the real key yourself, in Infisical (or 1Password), without it ever passing through this chat.
You will never paste your actual API key into this chat with Claude. The real value gets typed directly into your secret manager. If Claude offers to enter the real key for you, say no. This rule has no exceptions.
Using my Infisical setup, scaffold a secret in my Agent Secrets project by running infisical secrets set BRAVE_API_KEY=PASTE_HERE --env=dev. Use the literal placeholder PASTE_HERE so I can fill in the real value myself in the dashboard. I will never paste my actual API keys into our chat, only the PASTE_HERE placeholder. Do not offer to enter the real value for me. I always type real values directly into my secret manager myself. This rule has no exceptions.
Using my 1Password setup, create a new item in my Agent Vault called Brave Search API Key. Make it an API Credential item with a field called credential. Set the credential value to the placeholder PASTE_HERE so I can fill it in myself. I will never paste my actual API keys into our chat, only the PASTE_HERE placeholder. Do not offer to enter the real value for me. I always type real values directly into my password manager myself. This rule has no exceptions.
After Claude finishes scaffolding the entry, fill in the real value yourself:
- Infisical: Open
app.infisical.com, go to yourAgent Secretsproject, thedevenvironment. FindBRAVE_API_KEY, replacePASTE_HEREwith your actual Brave key, and save. - 1Password: Open the app, find the
Brave Search API Keyitem Claude just created, replacePASTE_HEREwith your actual Brave key, and save.
The agent does this for you, but only the safe part. Scaffolding the secret with the placeholder is mechanical work and Claude is faster at it than you are. Typing the real value is the part only you ever do.
Wire your config to read the secret at runtime
7 minNow the magic move. The real key never gets pasted into your Claude Code settings.json or any script. Your config references the secret by name, and the value resolves at runtime, held in memory for the moment it is needed, written nowhere on disk. The two tools resolve that reference differently, so follow your path.
Infisical (recommended): inject at launch. Infisical hands the secret to your process as an environment variable when you start it through infisical run. Your config just names the variable; Infisical fills it in:
"BRAVE_API_KEY": "BSA_xxxxxxxxxxxxxxxxxxxxxxxx"
"BRAVE_API_KEY": "${BRAVE_API_KEY}"
infisical run --env=dev -- claude
1Password: embed an op:// reference. Instead of the raw key, you paste an op:// reference directly into settings.json. When Claude Code starts, it asks 1Password for the value. You don't type the path by hand: in 1Password, right-click the field and pick Copy Secret Reference, then paste it in:
"BRAVE_API_KEY": "op://Agent Vault/Brave Search API Key/credential"
The values live only in that running process. Quit the process and they're gone. Nothing landed in a file. The agent does this for you. Ask Claude to open your settings.json and replace any raw API key with the matching reference for your path: ${BRAVE_API_KEY} for Infisical, or the op:// string for 1Password. You hand over the reference; Claude does the file edit. Real values never appear in chat.
Verify the agent can resolve the reference
5 minBefore you trust the reference, prove it works without exposing it. Read only the first four characters from the CLI, then stop. That confirms the reference resolves while keeping the full value out of your terminal scrollback.
infisical secrets get BRAVE_API_KEY --plain --env=dev | head -c 4 && printf '\n'
op read "op://Agent Vault/Brave Search API Key/credential" | head -c 4 && printf '\n'
You should see only a short prefix, not the full Brave key. That confirms three things in one move: your CLI is signed in (Infisical) or linked to the desktop app (1Password), the reference is correct, and the agent will be able to resolve the same secret when Claude Code starts.
head -c 4, treat that terminal scrollback as sensitive and avoid screenshots or sharing logs from that session.
secrets get returns nothing or "secret not found"? Check three things: you're in the directory you ran infisical init in, you passed the right --env (the secret was set in dev), and the value was actually saved in the dashboard (not left as PASTE_HERE). If the CLI says you're not logged in, run infisical login, pick Infisical Cloud (US Region), authorize in the browser, then retry.
op says "not signed in" or "no session"? Open the 1Password desktop app. Go to Settings, then Developer, and enable "Integrate with 1Password CLI." Then try op whoami. If that returns your account email, retry the non-revealing op read ... | head -c 4 check. On Windows you may also see this if you ran winget and never opened the app for the first time. Open it, sign in once, then retry. If op read says "could not resolve item," the path is wrong. Right-click the field again, Copy Secret Reference, and paste it fresh. Vault and item names are case-sensitive.
Lock down the .env bridge (only if you're using one)
10 minSkip this step if you wired everything through Infisical or op:// in Step 03 and have no .env file. Some project apps (Next.js, certain Node scripts) expect a .env file on disk and won't reach into your secret manager on their own. In that case you use a locked, gitignored .env file as a workshop bridge, and migrate the keys into Infisical (or 1Password) once the build is stable. Tip: on the Infisical path you can often skip the file entirely: run the app with infisical run --env=dev -- <your start command> and Infisical injects the same variables a .env file would.
The safest flow is the same shape as Step 02: Claude scaffolds the file with placeholders, you fill in the real values yourself, never through chat.
In my current project, create a .env.local file with placeholders only. Use the literal text PASTE_HERE for any value. Add a matching .env.example with the variable names and empty values. Update .gitignore so .env and .env.local are ignored but .env.example is committed. Then run chmod 600 on the real files and verify with git check-ignore -v .env .env.local that they are invisible to git. I will paste the real values into the files myself afterward. Do not offer to enter real values on my behalf.
The gitignore stanza Claude writes should look like this:
.env
.env.local
.env.*.local
.env.*
!.env.example
!**/.env.example
The ! lines re-allow safe example files with blank values, which is what teammates use to know what variables your project expects.
The agent does this for you. All the file scaffolding (creating .env.local, writing the .env.example, updating .gitignore, running chmod 600) is mechanical. You only ever paste real values into the file yourself, with your text editor open directly.
git check-ignore -v .env .env.local prints nothing? Your .gitignore change didn't land. Open .gitignore, confirm the stanza is there, save, and rerun the check. If it still prints nothing, you have a global gitignore conflict. Ask Claude to dump git config --get core.excludesfile and debug from there. Do not add real keys until the check confirms the files are ignored.
Smoke test with a real call
10 minThe last move is the proof. Run something that actually uses the credential and confirm it works without the real key ever appearing in your config, your prompts, or your terminal history.
Write a one-shot script that reads BRAVE_API_KEY from its environment, hits the Brave Search API for the query "agent native os", and prints the top three result titles. Run it with infisical run --env=dev -- <the script> so Infisical injects the key. Do not print the API key in your output. After it runs, confirm to me that the call succeeded and the key never appeared in any file you wrote.
Using the op:// reference for BRAVE_API_KEY in my settings.json, write a one-shot script that hits the Brave Search API for the query "agent native os" and prints the top three result titles. Run it. Do not print the API key in your output. After the script runs, confirm to me that the call succeeded and the key never appeared in any file you wrote.
If the script returns three result titles, you have a complete loop: the key lives in your secret manager, the config references it by name, the agent resolves it in memory, and the API call succeeds. The real value never touched disk.
One-paste setup: install the secrets-leak guardrail.
The six steps above set up where your keys live. This last move installs the protection that stops an agent from printing them. The workshop repo ships a default ## SECRETS rule in your CLAUDE.md plus a deterministic cross-platform PreToolUse hook (.claude/hooks/secrets-guard.js, Mac and Windows) that blocks secret-exposing commands (cat .env, unmasked op read, infisical secrets ... --plain, printenv/echo of secret-looking vars) before they run, while allowing the masked ... | head -c 4 form. Paste the prompt below into Claude Code and it installs that protection globally, so it covers every project, not just this repo.
You're upgrading AI Build Lab's secrets-leak protection to the new cross-platform guard. Work
carefully, never print a real secret value, and report each step.
1) Get the latest workshop repo (it now ships the upgraded guard):
cd ~/GitHub/agent-native-os && git pull
If git pull says "Already up to date" but the file in step 2 is missing, my repo is missing the
upstream remote. Add it and merge:
git remote add upstream https://github.com/aibuild-lab/agent-native-os.git 2>/dev/null || true
git fetch upstream && git merge upstream/main
2) Confirm the new guard arrived: .claude/hooks/secrets-guard.js exists (it replaces the older
block-secret-print.sh), .claude/settings.json has a PreToolUse hook (matcher "Bash|PowerShell")
that runs it with node, and CLAUDE.md has a "## SECRETS" block. If any is missing, stop and tell me.
3) Install it GLOBALLY so it protects EVERY project, on Mac and Windows:
- mkdir -p ~/.claude/hooks
- copy .claude/hooks/secrets-guard.js to ~/.claude/hooks/secrets-guard.js
- back up ~/.claude/settings.json (cp to ~/.claude/settings.json.bak), then ADDITIVELY add a
PreToolUse hook with matcher "Bash|PowerShell" whose command runs the guard with node at the
absolute path ~/.claude/hooks/secrets-guard.js. Preserve every existing setting, keep valid JSON.
If that hook is already wired, skip.
4) Clean up the OLD guard if you installed it before: if ~/.claude/hooks/block-secret-print.sh
exists, remove it and remove any PreToolUse entry that points at block-secret-print.sh, so you
are not running both. Leave everything else untouched.
5) Test the global guard: pipe a sample PreToolUse payload and confirm it BLOCKS a secret-printing
command and ALLOWS a normal one (a block prints a JSON "permissionDecision":"deny"):
printf '%s' '{"tool_name":"Bash","tool_input":{"command":"cat .env"}}' | node ~/.claude/hooks/secrets-guard.js
printf '%s' '{"tool_name":"Bash","tool_input":{"command":"git status"}}' | node ~/.claude/hooks/secrets-guard.js
6) Report what you changed and the test results. If I ever pasted a real API key into a chat,
remind me to rotate it.
git pull.
The key rotation path.
API keys expire, leak, or just need to change. When that happens, your job is small: update one place, restart one process. The config never changes because it only ever held a reference.
- Generate a new key in the service (Brave, Gmail, Notion, whatever provider).
- Open the secret in Infisical (or the item in 1Password). Replace the value. Save.
- Restart the process. On Infisical, relaunch through
infisical runand the fresh value is injected; on 1Password, restart Claude Code and the next session resolves theop://reference. Either way the config never changed.
If you are using the .env bridge, you also update the value in .env.local. The file stays gitignored, so the rotation never goes to GitHub.
Rotate immediately if you ever notice any of these: a real key ended up in a Claude chat reply, a config file with a real key got pushed to GitHub, or git check-ignore -v .env ever prints nothing while you have real values inside. The old key is compromised; the rotation closes the hole.
When it works.
You'll know the build is real when all of these are true at the same time:
- Your
Agent Secretsproject in Infisical (or yourAgent Vaultin 1Password) holds at least one credential, named clearly, with the real value typed in by you. - Your
settings.jsonhas zero raw API keys. Every credential slot holds an environment-variable name (Infisical) or anop://reference (1Password). You can screenshot the file and post it publicly without thinking about it. - A non-revealing CLI check returns only the first four characters when you run it from your terminal:
infisical secrets get BRAVE_API_KEY --plain --env=dev | head -c 4orop read "op://Agent Vault/.../credential" | head -c 4. - A real API call works using only the reference. The Step 06 smoke test passed and the key never appeared in chat or in any file the agent wrote.
- If you used the
.envbridge,git check-ignore -v .env .env.localprints a match line for both files. The real values are invisible to git.
If all five hold, you are done with this card. Every later workshop build inherits the posture you just set up.
If you get stuck.
Common failure modes, ranked by how often they bit students in the live workshop:
- Infisical:
infisical runinjects nothing. Your script reports "missing BRAVE_API_KEY" even though the secret exists. Two usual causes: you're not running from the directory you raninfisical initin (no.infisical.jsonon hand), or you passed the wrong--env(the secret is indev, you asked forprod). Fix: run from the linked directory and match the environment the secret lives in. - Infisical: the secret is still
PASTE_HERE. The scaffold worked but you never filled in the real value. Openapp.infisical.com, yourAgent Secretsproject,devenvironment, and replacePASTE_HEREwith the real key. The non-revealinginfisical secrets get BRAVE_API_KEY --plain --env=dev | head -c 4check should then return the expected prefix, not the placeholder. - Infisical: CLI says you're not logged in. The installer signs you in, but sessions can expire. Run
infisical login, pick Infisical Cloud (US Region), and authorize in the browser. If the browser can't auto-push the token, copy it from the page and paste it into the CLI when prompted. - 1Password: password loop. The CLI keeps asking for your account password and never seems to cache. Cause: the desktop app integration is off. Fix: open the 1Password app, go to Settings, Developer, and enable "Integrate with 1Password CLI." Then run
op signinonce. After that the CLI uses your unlocked desktop session. - 1Password:
op://reference doesn't resolve in Claude Code. Claude reports "missing BRAVE_API_KEY" even though the reference is insettings.json. Cause, usually: Claude Code launched before the 1Password app was unlocked. Fix: unlock 1Password, then restart Claude Code. The session resolves references on startup, not on every call. (Related: if "Copy Secret Reference" is missing, change the item type to "API Credential" with the value in a field calledcredential.) .envgot accidentally committed. You rangit add .before the gitignore rule landed and now your real key is in the git history. The key is compromised. Rotate it in the service immediately, update your secret manager with the new key, and rungit rm --cached .envfollowed by a fresh commit. Force-pushing to overwrite history is the next move if the repo is private and the leak is recent.- You skipped Step 04 and the build looks wrong later. Always verify the secret resolves before trusting it, but never by printing the full key. A wrong reference silently passes the empty string to your script and the API call returns "401 unauthorized." Five minutes verifying saves an hour debugging.
Where to go next.
Credentials are out of reach. Three natural next moves: