Every dev tool you grant API access to, AI assistants included, can read the keys within its reach. No setup removes that risk entirely, so the goal is fewer secrets exposed and less damage when one leaks.
Developer workstations accumulate API keys and other secrets that malware can read from .env files, shell history, and saved CLI credentials. An infostealer only has to steal the key, and using it skips the second authentication factor a person would need. AI agents increase the risk, since they generally require broad access to be useful.
For example, attackers behind the s1ngularity attack compromised nx, a popular JavaScript build tool, and pulled API keys and SSH keys from over a thousand developer machines. The attackers also weaponized developers’ AI coding agents, prompting installed CLIs to comb the filesystem for secrets.
Several free open-source tools can help reduce the number of secrets you leave exposed and limit the damage when one of them leaks.
Start by seeing what’s already exposed.
Before you change anything, scan your workstation to learn where secrets already live. A good starting point is bagel, which reports secrets and insecure settings across your system, including AI tool credential files, cloud keys, and unsafe Git or SSH configurations.
For secrets already committed to Git, a verifying scanner such as TruffleHog can not only locate the access keys but also test them against the provider to determine whether they still work.
Your first scan will probably find more secrets than you remember creating. Re-run it after each cleanup step to confirm the count drops.
Aim for four reachable wins.
Your tools need access to the API keys and tokens to do their job, so reduce both the chances they’re abused and the damage when one leaks. To do that:
- Keep secrets out of plaintext files.
- Stop them from spreading into Git and logs.
- Require your approval before a sensitive key gets used.
- Minimize the damage if a key leaks.
Your exposure and convenience depend on where you put your secrets, so let’s start there.
Weigh exposure against convenience.
You can keep a secret in several places, from a plaintext file to a vault that prompts you each time. More protection usually means less convenience, so the right store depends on the key’s sensitivity, what you’re defending against, and how much inconvenience you’re willing to tolerate.
A secret’s exposure in a store is based on two factors:
- Whether software running as you can read it without your approval, and
- How many other secrets an attacker gets by compromising that store.
The table below rates the store options on both, so you can pick the approach that works for you.
| Approach | Silent read by malware running as you? | Blast radius of one compromise | Automation / headless | Key tradeoff |
|---|---|---|---|---|
| Plaintext File (e.g., .env) | Yes | The keys in that file | Works everywhere | Most leaks start here |
| OS Keychain | Yes, while unlocked | That store’s items | Good, auto-unlocked | Once unlocked, code running as you can read it, with a per-item prompt on macOS |
| Password Vault (e.g., 1Password) | No, each use needs approval | The whole authorized account | Poor, needs a person to approve | One approval, or an open session, exposes the account |
| Scoped Password Vault | No | Only that one vault | Poor, still interactive | Needs an extra limited identity to set up |
| Service Account | The token sits at rest | Only its granted vaults | Good, non-interactive | The token unlocks everything in its scope |
The OS keychain is convenient but stays unlocked.
If you’re not sure where to start, the OS keychain is a good default, since infostealers often focus on plaintext files. Every major desktop platform includes one:
- macOS: The macOS Keychain, driven by the security command.
- Windows: Credential Manager, via cmdkey.
- Linux: The Secret Service, via secret-tool.
The keychain is unlocked the entire time you’re logged in, which is both convenient and risky. The convenience is that your tools read a key without prompting you, even ones running in the background or headless. Your system usually unlocks it at login and holds it open for the session, so code running as you can read the keys it stores. On Windows and Linux, that code reads the store without prompting. macOS adds a per-item prompt when an app tries to access something it didn’t store, though there are ways around it. A file scraper still finds nothing, but code that reads the keychain directly gets the key.
Getting the secret to a tool is its own task.
How you deliver a secret depends on how the tool is started. If you start the tool yourself, you can inject the secret as you launch it. A tool that another program spawns or runs headless needs an auto-unlocked store or a lookup at the moment of use.
You can get the secret to a tool in three ways:
- You look it up at the moment of use, for example, using
security find-generic-passwordor 1Password’sop read. - You inject it into the tool’s environment when you launch the tool.
- The tool reads it from its own config file, where you’ve replaced the secret with an environment variable reference.
You can use these methods with any store you choose.
A password vault adds a per-use checkpoint.
For the few sensitive keys you want to approve each time, use a vault such as 1Password instead of the OS keychain. Unlike the keychain, 1Password asks for your biometric approval at the moment a tool needs the key, and caches it only for a short session. You replace the literal key with a 1Password secret reference in a config or env file, and 1Password’s CLI resolves it to the value when a tool needs it.
The downside of using 1Password is that once you give the tool access, it can read all data stored in your 1Password account, not just the secret you have in mind. As a result, if you or your AI tool is tricked into requesting access, you might inadvertently give the requesting software access to a lot of sensitive data.
To lower your exposure, consider creating a 1Password vault just for the secrets you use for your dev work. Then, create a limited 1Password identity with access restricted to that one vault. Cleanly doing that requires a service account that’s available only to 1Password business customers. You can mimic this approach using a guest account on a personal plan.
On a personal plan, the guest route needs one more step. You need to turn off the app’s biometric CLI integration and sign in as the guest from the terminal. Signing in manually requires a password for the guest account and doesn’t work with 1Password biometric authentication.
For a hybrid approach, keep everyday and automated secrets in the OS keychain. Reserve a scoped vault for the few keys you want to explicitly approve. This gives you low-friction storage for your routine keys and a per-use checkpoint for the few that matter most.
Stop secrets from sprawling into Git, history, and transcripts.
By the time you store a secret well, your tools have already written copies of it into Git, your shell history, and AI transcripts. Clean them up, then keep them clean:
-
Git: Once you commit and push a secret to a repo, deleting the line or rewriting history only reduces the trace, so treat it as burned and rotate it. To catch the next secret before you commit it, run a scanner such as gitleaks as a pre-commit hook, which blocks any commit that contains a secret.
-
Shell history: Your shell records the commands you type, including any key you paste on a command line. Keep the secret off the command line and put a reference there instead, so your shell resolves the secret only when the command runs. When a tool wants the key as an argument, read it inline, as in
mytool --token "$(op read 'op://...')". When a script reads the key from the environment, export a reference the same way, as inexport GITHUB_TOKEN="$(op read 'op://...')". When you must type the secret directly, fall back to history hygiene, such as Zsh’ssetopt HIST_IGNORE_SPACE, which drops any command you prefix with a space. To clean keys already in your history,bagel scrubredacts them in place. -
AI session transcripts: AI tools log your sessions, and a secret you paste into a prompt ends up in those logs. Scrub them with a tool built for it, such as
bagel scrub, which replaces secrets with redaction markers and leaves the conversation readable.
Even after you store secrets well and scrub old copies, your AI tool can still read whatever’s on disk, the same access the s1ngularity attack turned against developers. Blocking the tool from credential paths is a separate defense from storing them properly. Even an agent hijacked by a prompt or a bad package then finds nothing to read there. The Trail of Bits Claude Code config, which the Personal AI Stack points to, blocks reads of common credential paths.
SSH keys and config-file keys need their own handling.
Keeping a secret in a store and handing it to a tool on demand doesn’t work in all scenarios. For an SSH private key or a key that a tool reads from its own config file, such as npm’s .npmrc, handle each on its own terms:
-
SSH keys: Move your private keys into an SSH agent such as the 1Password SSH agent. It authenticates your SSH connections, including Git over SSH, so the private key never leaves the vault. You approve each attempt to use a key, which grants access only to that key, not the rest of your 1Password account. Alternatively, on a Mac, Secretive stores SSH keys in the Secure Enclave, where even software running as you can’t export them; it prompts for strong authentication each time a key is accessed.
-
Config-file keys: A package manager or CLI may read its key from a file it owns. For example, npm has
${VARIABLE_NAME}support for .npmrc files. When a tool can’t reference a variable, lock that file down with strict permissions, keep it out of Git, and rely on scoping and rotation to limit what a stolen copy is worth.
Where to start.
You don’t have to do all of this at once. Here’s one way to order your efforts:
- Scan your workstation to see what’s exposed.
- Rotate anything that ended up in Git, a shared drive, or a cloud location, since you have to assume it leaked.
- Put everyday and automated keys in your OS keychain, and move your most sensitive interactive keys into a scoped vault.
- Keep secrets off the command line, and add a pre-commit scanner so they can’t slip into Git.
- Scrub the secrets already in your shell history and AI transcripts.
- Move SSH keys into an agent.
- Scan again to confirm the count dropped.
Keep fewer secrets within reach, and match each one to its sensitivity and how it’s used, so a single leak stays small.

