SELECT 1
Entry 007Sat 2026.10.17
One launch a day. Picked, run, explained.

Sat 2026.10.17

Heldkeys: finding an agent’s credentials without showing their values

Today's pick: Heldkeys

Heldkeys is a product that runs in the same shell as a coding agent and lists the credentials the agent can read. It reads environment variables, cloud CLI logins, registry tokens and .env files. It classifies the credentials by severity and lifetime and shows at most the first 4 and last 3 characters of a value.

It is today's pick because its promise never to print a secret could be checked directly with 28 planted fake secrets.

exit 0Ran itIt works. It classified 32 credentials in 10 ms and exposed no secret value.
Found on
Show HN
Built with
Rust, clap, regex, serde, cargo-dist

The problem

A coding agentAn AI program that reads and edits code and runs commands for a developer. Claude Code and Codex are examples. runs in a shell under the developer's account. That shell holds every login the developer has accumulated: the access key in ~/.aws/credentials, the publish token in ~/.npmrc, the GitHub CLI's hosts.yml and the project's .env file. If a prompt injection in an issue makes the agent run one shell command, that command can read every credential the developer can read.

GitHub's secret scanning is a widely used tool for finding secrets. GitHub's official documentation describes secret scanning on the Secret scanning concept page (docs.github.com). It scans the entire Git history on all branches of a repository. It looks for hardcoded credentials such as API keys, passwords and tokens. Push protection blocks pushes that contain a secret. When validity checks are enabled in the repository or organization settings, secret scanning checks with the issuing service whether a credential has been revoked. It covers what enters the repository. The logins in a developer's home directory and shell environment are not committed. They are on no branch, so they fall outside secret scanning. Yet the agent runs in this environment.

How it solves it

Heldkeys, by Hugo Nogueira, moves the inspection from the repository to the agent's shell. It runs in the same shell as the agent and finds the credentials the agent can read. It checks environment variables by name (*_TOKEN) and by value format (ghp_, AKIA). In the home directory it reads CLI logins (AWS, gcloud, Azure, kube), Git and GitHub logins, and registry tokens (npm, PyPI, cargo, Docker). It also reads SSH private keys without a passphrase and the configs and logins of agent tools such as Cursor and Claude Code. Each finding carries a rule id, a severity, a lifetime (long-lived, short-lived, unknown), an access level (write, read, unknown) and a fix.

There are three commands. scan inspects the current state of a laptop or sandbox. ci checks the job's runtime exposure and statically analyzes the agent steps in workflow files. It checks whether an agent step receives secrets it does not need or runs in a checkout of pull requestA request to merge the changes on one branch into another. Other developers review and approve it. code. It also checks whether the token permissions are broad and whether the action is pinned to a commit hash. preflight prints a single PASS or FAIL line in a container entrypoint and blocks the run with its exit code. The repository ships a GitHub Action and a Claude Code skill, so an agent can run the check on itself.

What is different

GitHub's secret scanning inspects what has been pushed to the repository, centrally and continuously. Heldkeys inspects the home directory, environment variables and mounted tokens of the machine it runs on. It must run on each machine and in each sandbox to read the same files as the agent. It inspects only the locations it knows. Today a GitHub token planted in ~/.config/myapp/token.toml matched none of its 26 check modules and did not make the list. GitHub's secret scanning is better at finding every secret that has entered a repository.

Secret scanning reports values that match a pattern and confirms them with the issuer through validity checks. Heldkeys judges lifetime and access from the kind of value. It rates a cached AWS SSO session as Medium because it is short-lived. It rates a static access key as High because it is long-lived. It does not check with the issuer, so access is often unknown. Today it classified the GitHub token in the environment as "long-lived, unknown access".

Heldkeys output goes straight into an agent's chat window and into CIContinuous integration: a setup that builds and tests code automatically every time it is pushed. logs. So it never prints a value. It shows a fingerprint of the first 4 and last 3 characters (AKIA…PLE) only for token formats with a public prefix. The trade-off is that keys are harder to identify. Several keys of the same format must be told apart by location. A value of unknown format, such as a password, gets no fingerprint at all.

How it was built

  • Hugo Nogueira alone made all 6 commits on 2026-10-16. Five versions, 0.1.1 through 0.2.3, were released that day. Nothing in the commits or documents says it was built with AI.
  • 25,121 lines of Rust, 26 check modules, 94 rules and 450 test functions. Heldkeys depends on seven crates (clap, owo-colors, regex, serde, serde_json, toml and yaml-rust2), plus libc on Unix.
  • A test checks that no secret appears in output. It runs every fixture through every output format on stdout, stderr and --output files. It fails if any output contains a ten-character slice of a secret value. A slice can start anywhere in the value, so a partial exposure is caught only if it is 10 characters or longer. Fixture tokens are committed as {{fake:prefix|rest}} markers, so gitleaks reports nothing on the repository.
  • Releases are built with cargo-dist: static musl binaries for Linux and binaries for macOS, each with a SHA-256 file and build provenance. The project's own CI runs heldkeys ci, zizmor and gitleaks.

Running it

  1. Build. cargo build --release finished in 1 minute 10 seconds, and the binary is 4.0 MB.
  2. Fake home. I copied the 20 files in the repository's tests/fixtures/full. I used the Perl one-liner from demo.tape to replace the {{fake:…}} markers with complete fake tokens. I added two .env files, a fake OpenSSH key without a passphrase and 3 workflow files, for 26 files in total. I cleared the environment with env -i and added only two fake tokens.
  3. Summary scan. The scan found 32 credentials. Of these, 11 were long-lived with write access. It took 10 ms (three runs: 10, 12 and 10 ms). Heldkeys classified the findings into eight categories by location. It grouped the GitHub token in the environment with the same token in Cursor's mcp.json as "same secret in several places".
  4. Gate. --fail-on long-lived-write --quiet printed one line and returned exit code 1. preflight did the same.
  5. ci. Three workflows (the demo, the vulnerable example and the hardened example) produced 11 risks (8 High, 3 Medium). The risks included secrets the agent step does not need and a checkout of pull request code. The others were a shell with write access, broad token permissions and an action not pinned to a hash. With only the hardened example present, it reported 0 risks and exited with code 0.
  6. Leak check. I made 463 ten-character slices from the 28 planted secret values and searched for them in 15 output files. None of them was found. The database password and the Docker password showed no fingerprint at all.
  7. Read-only and network. The SHA-256 of all 26 files was identical before and after the scans. Under strace, 0 of 42 openat calls carried a write flag, and --output created only the named file. I removed HELDKEYS_NO_NET and traced network calls. The only TCP connection was to 169.254.169.254:80.
  8. More planting. Heldkeys detected a ghp_ token and an AKIA key in oddly named environment variables by their value formats. It reported an ordinary password value as a "Possible credential" with no fingerprint. A fake OAuth token in ~/.claude/.credentials.json was rated High.
$ heldkeys
heldkeys: 32 credentials reachable from ~/code/app (11 long-lived with write access)

Fix these first
  HIGH    Anthropic API key                     env: ANTHROPIC_API_KEY                  sk-a…AKE
  HIGH    GitHub OAuth token                    ~/.config/gh/hosts.yml (github.com)     gho_…013
  HIGH    Plaintext git password                ~/.git-credentials (git.example.com)
  HIGH    Long-lived AWS access key             ~/.aws/credentials [default]            AKIA…PLE
  +27 more credentials, see --details

Where they are                                     high  medium     low
  Shell environment                                   2       -       -
  Cloud CLIs (AWS, gcloud, Azure, kube)               7       5       -
  Git and GitHub                                      4       3       -
  Package registries (npm, PyPI, cargo, Docker)       4       1       -
  SSH                                                 1       -       -
  MCP and agent configs                               1       -       -
  Sandbox and host                                    1       -       -
  Project .env files                                  2       -       1

32 credentials reachable, 11 long-lived with write access.
If a prompt injection can run one shell command here, it can use all of them.

Not checked

  • A real home directory. The rules allowed only the fake home, so false positives and misses on a real machine are unknown.
  • scan --docker and --kube, the ssh-agent check, and macOS paths. They need a machine with a Docker daemon and ssh-agent running, and a Mac.
  • The 450 tests, the GitHub Action, SARIF output, baselines (--write-baseline) and accepted risks in .heldkeys.toml. Next time, make a baseline, plant one more secret, and check that it is reported as a new finding.

Verdict

It works. It classified 32 credentials in 10 ms and exposed no secret value. It checks only the locations it knows, though, so a token under an unfamiliar path is not found. It is worth a try if you want to know what a coding agent can read from your shell before you run the agent under your account. It also fits if you want the same check as a gate in CI and container entrypoints. If the job is finding secrets already committed to a repository, this product is not the right choice. The README's comparison table also leaves that job to TruffleHog and gitleaks.

What to take away

  • Put every output of a tool that handles secrets through an automatic check. Today I checked the output the same way Heldkeys’ own test does and found none of the 463 ten-character slices.
  • Do not commit fake test tokens in complete form. Version 0.2.3 switched to {{fake:…}} markers so that gitleaks reports nothing on the repository.
  • Confirm a read-only claim with hashes and strace. Today the hashes of 26 files were identical before and after, and 0 of 42 openat calls used a write flag.
  • When you give an agent a tool, list the forbidden actions in the skill too. The bundled skill document tells the agent not to run cat or env to confirm a finding.

Getting started

  1. git clone https://github.com/hugomn/heldkeys && cd heldkeys
  2. cargo build --release
  3. target/release/heldkeys
  4. target/release/heldkeys --details
  5. target/release/heldkeys ci
You need
Rust 1.88 or newer (rust-version in Cargo.toml) · Use the static musl binary from a release instead of building from source. It comes with a SHA-256 file and build provenance.
Cost
MIT and free. There is no pricing page.

It reads the environment variables and home directory of the shell it runs in. So today I ran it with env -i and passed a fake home as --home and --root. HELDKEYS_NO_NET=1 disables the single TCP connection to the cloud metadata address.

Steps select 1 ran.

Progress

  1. Day 3Active
  2. Day 7Quiet
  3. Day 15Upcoming
  4. Day 30Upcoming
  5. Day 90Upcoming
  6. Day 180Upcoming
  7. Day 365Upcoming

Latest check Day 7, Fri 2026.10.23

Commits since launch
5 → 15
Stars
4 → 6
Last commit
2026.10.17

No commits in this period.

Full history →