Every dev tool you grant API access to, AI assistants included, can read the keys within its reach. You can't make a key unstealable by software running as you, 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.
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.
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:
Your exposure and convenience depend on where you put your secrets, so let’s start there.
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:
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 |
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:
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.
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:
security find-generic-password or 1Password’s op read.You can use these methods with any store you choose.
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.
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 in export GITHUB_TOKEN="$(op read 'op://...')". When you must type the secret directly, fall back to history hygiene, such as Zsh’s setopt HIST_IGNORE_SPACE, which drops any command you prefix with a space. To clean keys already in your history, bagel scrub redacts 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.
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.
You don’t have to do all of this at once. Here’s one way to order your efforts:
Keep fewer secrets within reach, and match each one to its sensitivity and how it’s used, so a single leak stays small.