A coding agent running in your terminal runs as you. It can read what you can read and use what you can use: your cloud logins, your git and package tokens, the .env files in every repository you have cloned, the API keys you pasted into MCP server configs months ago. Most of the time that is fine, because the agent does what you asked. Prompt injection changes that. Text the agent reads along the way, in an issue, a web page or a README, can talk it into running a command, and whatever that process can reach, an attacker can then use.
At work, our agents run code against customer systems from a sandbox where nothing can authenticate. I wrote up that design in A Capability, Not a Credential. A few weeks later I asked the same question about my own laptop, where I run coding agents every day, and I could not find a tool that answered it. Secret scanners are very good at telling you whether a secret was committed to a repository. I wanted to know what a process running as me could use right now, including credentials no repository has ever seen, like a cloud CLI login or a token exported in my shell profile.
So I built heldkeys. You run it where an agent runs, and it tells you which keys that agent is holding, how dangerous each one is, and how to shrink the list.
What it looks at
On a laptop, heldkeys reads the places credentials actually live: environment variables, the AWS, gcloud, Azure and Kubernetes CLI configs, git credential files and helpers, the GitHub CLI login, npm, PyPI, cargo and Docker registry tokens, SSH keys without a passphrase, .env files in the project, and tokens sitting in the MCP configs of Claude Desktop, Claude Code, Cursor, VS Code and others. It rates each finding by how long the credential keeps working and whether it can change things, since a long-lived token with write access is a much bigger risk than a short read-only session.
The default view fits on one screen. It lists the credentials to fix first, then a small table of where the rest are, the secrets that appear in several places, and a few next steps. Everything else is one --details away, or in JSON, Markdown or SARIF.
Inside a sandbox or container, the same binary checks what the sandbox exposes from the inside, such as a mounted Docker socket (effectively root on the host), a Kubernetes service account token or a reachable cloud metadata endpoint. heldkeys preflight makes that a pass or fail check for an entrypoint, so an agent never starts in a sandbox that holds a long-lived write credential.
Agents in CI
I think the newest risk is in GitHub Actions. Teams are adding AI agents to workflows that respond to issue comments and pull requests, and some of those workflows hand the agent a write token or a set of repository secrets while it reads text anyone on the internet can write. A few of this year’s coding-agent vulnerabilities followed that shape, including agents that checked out a pull request’s code and then loaded the attacker’s own agent configuration from it.
heldkeys ci looks for that combination. At runtime it lists what the job can reach: secrets in its environment, the GITHUB_TOKEN, and whether the job can mint cloud credentials over OIDC. It also reads the workflow files and flags agent steps that run on untrusted triggers with secrets or write permissions, agents working inside a checkout of an outside pull request, unpinned agent actions and unrestricted shell access. It follows local reusable workflows too, so a dispatcher that hands every repository secret to a review workflow is judged with the dispatcher’s triggers and permissions. For workflow security in general, zizmor already does a great job, so heldkeys only has rules for agent steps.
Running it on my own machine
I ran it on my own laptop first. It found the usual things that build up over the years: an old plaintext git credentials file, a token exported in my shell profile, SSH keys without a passphrase. I would not have listed any of them if you had asked me, and every agent I started could have used them. Cleaning up took about twenty minutes. Git now uses the system keychain, the package token lives in the keychain and only loads inside the projects that need it, and the keys have passphrases.
Launch day had a small irony of its own. GitHub’s push protection blocked the first push of the repository because the test fixtures contain fake Slack tokens. A tool that looks for credentials needs credential-shaped test data, and I was glad to see the platform catch it.
Design choices
A tool that reads every credential on your machine has to earn trust. Values only ever appear as short fingerprints, and only for token formats whose prefix is public; a test runs every fixture through every output format and fails if any part of a fake secret shows up. There is no telemetry, no update check and no call to the providers behind your credentials, and heldkeys does not write anything to your machine. Everything it scans is treated as hostile, because in CI it reads files from pull requests written by strangers. Output is sanitised so a crafted workflow cannot inject terminal escapes, fake log annotations or links into a job summary, and parsing is bounded so a small malicious file cannot exhaust memory or CPU.
It is written in Rust and ships as a single binary, static on Linux, so it drops into any container. Some things are out of its reach: browser sessions, secrets that only exist inside the OS keychain, and anything in git history, where gitleaks and TruffleHog are the right tools. The threat model covers the rest.
Try it
brew install hugomn/tap/heldkeys
heldkeys
There is also a one-line install script, prebuilt binaries with provenance, cargo install heldkeys, and a GitHub Action with SARIF output for code scanning. If you run it, I’d love to hear what surprised you and which credential locations it should learn next.