How to Use Heldkeys: The Beginner's Guide (the Inventory Lesson, First Audit, the CI Arc, and the Remediation Arc)
CodexFuse Team · 2026-11-02T14:10:58.756Z · 16 min read min read
Heldkeys is the read-only, open-source credential auditor for AI agents: one Rust binary that inventories what your agent can reach and flags risky agent steps in GitHub Actions. This beginner's guide covers install, your first audit, the CI arc, and remediation.
What Is Heldkeys, Exactly?
Heldkeys is a read-only, open-source Rust CLI that answers one question: which credentials could an AI agent reach from the place it runs? It inventories the credentials visible to your agent on your laptop, inside a sandbox, or in a CI job — environment variables, cloud CLI logins, AI tool logins, .env files, SSH keys, MCP tokens — and flags risky AI agent steps in GitHub workflows. It never prints a single secret: values appear only as fingerprints, or not at all.
If you are learning how to use Heldkeys, the mental model comes first. Your agent inherits everything in its process environment. If you hand Claude Code a task from your terminal, the agent sees what your terminal sees. The author's own framing, from the README: "If a prompt injection hijacked the AI agent running here, which credentials could it use?" And the corollary that makes the point unmissable: "If a prompt injection can run one shell command here, it can use all of them." This Claude Code walkthrough shows the agent's environment in action — Heldkeys is the twenty-minute audit you run on it before anything risky happens. For the exact-match crowd: this is the heldkeys tutorial the field was missing — a single unified walkthrough from install to first audit to CI to remediation.
One disambiguation, once: TanStack's useHeldKeys is an unrelated React hook that tracks pressed keyboard keys. Different product, different universe. This guide is about hugomn/heldkeys, the credential auditor — run an ai agent credential audit against your own environment and you will see exactly what the agent can reach, which is why this page is the pillar of the AGENT-CREDENTIAL-AUDIT lane.
How Do You Use Heldkeys, Step by Step?
- Install the single binary with Homebrew, the installer script, or
cargo install— Linux and macOS, one self-contained file, done in well under a second. - Run
heldkeysin the same terminal your agent uses — Claude Code, Codex, Cursor, Aider — because that is the environment the agent inherits. The defaultscansubcommand inventories everything, read-only. - Read the inventory report in three groups: machine credentials (env vars, cloud logins, tokens, SSH keys, MCP configs), agent risks, and host checks.
- Rescan a project or user with flags:
--pathfor another project's.env/MCP configs,--homefor another user's profile. - Run the CI arc:
heldkeys ciin a GitHub Actions job inventories the runtime environment and analyzes workflow files for the seven HK-WF agent-step rules. - Gate with
--strictwhen the report must be clean to proceed: it exits 1 on unreadable files (HK-READ-001) and un-analyzable workflows (HK-WF-007) instead of skipping silently. - Remediate what it finds: move long-lived secrets into scoped ones, delete the dead credentials, rotate the ones you touched while setting up.
That is the whole arc: install, first audit, flags, CI, remediation. The sections below walk each one with the exact commands and rules, all verified against the README on 2026-11-02.
How Do You Install Heldkeys on Your Machine?
Heldkeys is deliberately boring to install: one self-contained binary for Linux and macOS, no runtime, no daemon. The README's own justification for Rust: "Because the tool has to be one file you can drop into a sandbox, a distroless container or a CI runner, with no runtime and a scan that finishes in well under a second." Windows is not supported yet — the project is built and tested on Linux and macOS only; under WSL you use the Linux binary.
| Route | Command | Best for |
|---|---|---|
| Homebrew | brew install hugomn/tap/heldkeys | macOS / Linux laptops, fastest path |
| Installer script | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hugomn/heldkeys/releases/latest/download/heldkeys-installer.sh | sh | servers and CI runners without a package manager |
| From source | cargo install heldkeys --locked | Rust users; needs Rust 1.88 or newer |
| Prebuilt binaries | release assets with SHA-256 checksums and Sigstore attestations | air-gapped or pinned installs |
| GitHub Action | hugomn/heldkeys@v0 (pin to the full commit SHA for serious use) | the GitHub Actions arc below |
The prebuilt binaries ship as static musl Linux (x86_64 and aarch64) and macOS (Intel and Apple Silicon), each with SHA-256 checksums and Sigstore attestations per release. The GitHub Action has been available since v0.1.1; the README's advice is the moving major tag for casual use and a pinned full commit SHA when the audit actually gates something.
brew install hugomn/tap/heldkeys curl --proto '=https' --tlsv1.2 -LsSf https://github.com/hugomn/heldkeys/releases/latest/download/heldkeys-installer.sh | sh cargo install heldkeys --locked
After install, a bare heldkeys runs the default scan. The remaining flags from the README — --details / -v for verbose findings, --quiet to silence the report, --no-ignore, --config <file> / --no-config, --output <file>, and --no-color — are the usual knobs around output shape; the flags that change the audit's scope (--path, --home, scan --docker <container> / scan --kube <pod>) are what you reach for when the interesting environment is not the one your shell sits in.
What Does Your First Heldkeys Audit Report Contain?
Run heldkeys with no arguments and the scan subcommand inventories your machine in three groups. Here is what it reads — every row below is documented behavior, and the tool touches nothing outside its own process except a short, documented list (more on that in the guarantees section).
| Group | What it reads | Examples |
|---|---|---|
| Environment variables | Names matching *_TOKEN / *_API_KEY, DATABASE_URL, and known-format values | ghp_, github_pat_, AKIA, sk-ant-, sk-proj-, xoxb-, npm_ |
| Cloud CLI logins | AWS profiles, SSO sessions, credential_process, CLI cache, aws-vault, Granted; gcloud logins and ADC; Azure MSAL caches and service principals; kubeconfig users | Every cloud identity your terminal could present |
| AI tool logins | Codex auth.json; Claude Code .credentials.json plus settings env/apiKeyHelper; Gemini CLI OAuth and ~/.gemini/.env; GitHub Copilot apps.json/hosts.json; Hugging Face tokens (the macOS keychain is never read) | The tokens your agents themselves authenticate with |
| Dev-platform CLIs | Vault, Terraform, Vercel, Netlify, Fly.io, Supabase, Wrangler, Stripe (test-mode reported as Low), Doppler, Railway, Kaggle | Deployment and platform sessions |
| Package and git credentials | .netrc, .git-credentials, git credential helpers, GitHub CLI hosts.yml, .npmrc, .pypirc, Cargo credentials.toml, Docker registry logins | The classic leak surface |
| Git config | url.<base>.insteadOf rewrites, http.extraheader, remote URLs (home and project) | Silent redirects and embedded tokens |
| Project files | .env* up to four levels deep — including gitignored ones, skipping node_modules/vendor/.venv — plus VS Code Local History copies like .env_20260507230125 | The copies everyone forgets |
| SSH | Keys loaded in ssh-agent (fingerprints only, via ssh-add -l); passphrase-less private key files (only the first 256 bytes are read) | Never the key material |
| MCP tokens | Env, args, URLs, and headers of MCP configs for Claude Desktop, Claude Code, Cursor, VS Code, Windsurf, Zed, Codex, and Gemini CLI — home and project level | Project configs are noted because opening the directory starts their servers |
| Host checks | Docker socket reachability, a mounted Kubernetes service-account token, the cloud metadata endpoint (169.254.169.254:80 — one TCP connect, 300ms, no payload; disable with HELDKEYS_NO_NET=1) | Escape routes out of the box |
The author's launch post gives the format's payoff: the first time he ran it on his own laptop, it found the things that pile up over the years — an old plaintext git credentials file, a token exported in his shell profile, and SSH keys without a passphrase. Cleaning up took about twenty minutes. That is the tutorial-sized lesson of this tool: the audit is fast, the cleanup is the work, and most people have never seen the list.
Why Does Heldkeys Never Print a Secret?
This is the guarantee the whole tool rests on, so it gets stated exactly once, in the tool's own words: "Values appear only as fingerprints such as AKIA…PLE, and only for token formats whose prefix is public. Passwords and tokens of unknown shape get no fingerprint at all." The fingerprint rule, from the FAQ: at most the first 4 and the last 3 characters of a value, and only for known token formats whose prefix is public anyway (AKIA, ghp_, npm_, and friends). Anything of unknown shape — passwords included — gets no fingerprint at all.
The read-only promise is just as strict. Heldkeys never sends data anywhere — no telemetry, no update checks, no crash reports. It never writes to the machine except files you explicitly name with --output or --write-baseline. Outside its own process it only touches a short allowlist: ssh-add -l (with only SSH_AUTH_SOCK and PATH in the environment), Docker sockets (connect and close, no payload), the metadata endpoint, git ls-files (only for ci --repo-secrets), and docker image ls, php --version, node --version, lsof (incident mode only — never python), plus docker/kubectl copy-in for scan --docker/--kube. Nothing more.
How Does the GitHub Actions Arc Work?
If your search was really "github actions agent security check", this section is the answer. heldkeys ci does two things in a pipeline: it inventories the job's runtime environment (env secrets, GITHUB_TOKEN, OIDC — the ACTIONS_ID_TOKEN_REQUEST_* variables, meaning the job can mint cloud credentials — the Actions runtime token, a checkout-persisted .git/config token, GitLab's CI_JOB_TOKEN), and it analyzes workflow files against seven agent-step rules. A self-hosted runner bumps every severity one level, because the blast radius is bigger.
| Rule | What it flags | Severity |
|---|---|---|
| HK-WF-001 | An agent step on a trigger an outsider can fire (pull_request_target, issue_comment, issues, discussion, discussion_comment, pull_request_review, pull_request_review_comment, workflow_run) that holds secrets or a write token | High with a write token or unset permissions; Medium with secrets only; one level lower when the job only lets trusted actors start it |
| HK-WF-002 | An agent running in the fork's checkout on an untrusted trigger — the fork decides which MCP servers, CLAUDE.md, AGENTS.md, .claude/, .cursor/ the agent loads | High |
| HK-WF-003 | permissions: write-all or no permissions block at all | Elevated by default |
| HK-WF-004 | Excess secrets or secrets: inherit | Elevated by default |
| HK-WF-005 | An unpinned agent action | Medium |
| HK-WF-006 | Unrestricted shell (Bash(*), --dangerously-skip-permissions, --yolo) combined with a write token | High |
| HK-WF-007 | A workflow file that cannot be analyzed | Info — the rule never hides a step |
HK-WF-002 deserves a slow read because it is the CVE behind the tool. The README's context: a workflow that checks out a pull request's head and then starts an agent lets the fork decide which MCP servers that agent loads — the pattern behind CVE-2026-47751. An untrusted contributor does not need to exploit your agent; they only need to hand it their own configuration. This GitHub Actions guide covers the workflow mechanics themselves — Heldkeys is the security pass you add once agents join the pipeline.
One more rule worth knowing in this arc: HK-READ-001. When Heldkeys finds a file it cannot read or parse — too large, not valid YAML, a FIFO where a config should be, a project symlink that leads outside the project — it reports HK-READ-001 instead of skipping it silently. Severity Info, reported in every mode, and --strict turns it into a hard failure.
How Do You Gate a Sandbox or CI Job with Heldkeys?
Beyond scan and ci, two subcommands exist for gates. preflight is the sandbox gate: it exits 1 when findings reach the --fail-on threshold (default high). ci also exits 1 on a High finding. The threshold dial is --fail-on none|high|medium|low|long-lived-write — scan defaults to none, ci and preflight default to high — and --strict adds the fail-closed behavior on HK-READ-001 and HK-WF-007.
heldkeys preflight heldkeys ci --fail-on medium heldkeys --strict --format json heldkeys scan --docker my-sandbox --fail-on high
Exit codes are documented and scriptable: 0 means under the --fail-on threshold, 1 means the threshold was hit (or a --strict unreadable finding, or a confirmed incident indicator), 2 means a usage or runtime error. The --format text|json|markdown|sarif flag matters here — SARIF plugs into GitHub's code-scanning UI, and JSON feeds the skill: the repo ships a skill/heldkeys directory that teaches Claude Code, Codex, and Cursor to run heldkeys --format json on request without echoing values. And because the scan finishes in well under a second, adding it to a job costs nothing measurable. The sandbox mechanics themselves — isolating the environment you are auditing — are covered in this Docker walkthrough.
What Does "Incident <set>" Do, and When Would You Run It?
heldkeys incident <set> checks your machine for supply-chain indicators tied to a named disclosure. The first set is black-october, covering the October 19–22, 2026 disclosures: the LiteLLM 1.107.0 compromise, the OVHcloud CLI 0.11.0–0.15.0 binary replacement, the PHP and Node October security releases, and the Railway, Zeabur, and OpenCode breach notices. You run it after an incident lands to answer "am I carrying any of this" — exit code 1 on a confirmed indicator.
This is a niche subcommand, but it shows what the tool is really for: not just hygiene, but blast-radius questions with a deadline. The incident checks stay inside the same read-only guarantee — a handful of version lookups and file reads, never a payload that could phone home.
How Do You Remediate What the Report Finds?
The audit is the easy half. The remediation arc is what the report is for, and it has an order — not everything it finds deserves the same urgency. Work it in this priority, top first:
| Priority | Finding | The fix |
|---|---|---|
| 1 — stop the bleeding | Secrets in project .env files committed to git, or VS Code Local History copies of .env | Rotate the secret, remove it from history, add the file to .gitignore, delete the history copies |
| 2 — shrink the window | Long-lived tokens exported in shell profiles (.bashrc, .zshrc) or in agent .env files | Move them to a credential manager or short-lived issuance; never leave them in dotfiles the agent inherits |
| 3 — fix the pipeline | HK-WF-001/002 agent steps on untrusted triggers with secrets or write tokens | Pin permissions to the minimum, move the agent step off the untrusted trigger, pin the action to a SHA |
| 4 — lock the box | Passphrase-less SSH keys, .netrc/.git-credentials plaintext files | Add passphrases, move to SSH agent or the platform credential helper — see this Git beginner guide for the credential-helper setup |
| 5 — scope the cloud | Broad cloud CLI sessions (long-lived AWS profiles, full gcloud ADC) on a machine that runs agents | Short-lived, scoped-down credentials; know what VPS hosting is if your always-on agent runs on one, so its session scope is a decision, not an accident |
Then make the audit recurring: heldkeys ci in the pipeline, heldkeys preflight before an agent session starts on shared infrastructure, and a periodic baseline comparison so new credentials show up as diffs instead of surprises:
heldkeys --write-baseline baseline.json heldkeys --baseline baseline.json heldkeys ci --repo-secrets
How Does Heldkeys Compare to Secret Scanners and Workflow Linters?
Heldkeys is not a secret scanner in the gitleaks sense, and the README draws the line itself: gitleaks and betterleaks answer "was a secret committed to this repo or its history?", while Heldkeys asks "what is usable from here, right now?" TruffleHog verifies against providers over the network; Heldkeys never calls a provider and only does local inventory. Against workflow linters: zizmor covers workflow security broadly, and Heldkeys covers only the agent-step rules plus the job's runtime inventory — the two are meant to run side by side. It does not judge your tools the way Snyk's agent scan does; it reports the credentials around them. The closest prior art the README names is credential-reach.
The practical takeaway: you do not replace your existing scanner with Heldkeys. You run it alongside — the scanner watches the repo, Heldkeys watches the runtime the agent actually sits in.
What Can't Heldkeys See?
The FAQ is honest about the boundary, and the honest version matters for the keyword "what credentials can my ai agent access": the tool cannot see browser sessions, IDE tokens, what sits inside OS keychains, password-manager CLIs that are already unlocked, or cloud consoles in another tab. The README's own caution: a clean report means none of the known places expose anything — not that the agent holds nothing. Treat the report as a floor, not a ceiling: it proves the known places are clean, and the unknown places are still your problem.
Sources
The facts in this guide were verified against the following sources on 2026-11-02 — every version number, flag, rule, and quote comes from one of them:
- Heldkeys GitHub README (hugomn/heldkeys) — install paths, flags, subcommands, the inventory list, HK-READ-001 and HK-WF-001…007 rules, exit codes, read-only guarantees, the CVE-2026-47751 context, the black-october incident set, and all verbatim quotes — https://github.com/hugomn/heldkeys
- Hugo Nogueira's launch post "heldkeys" (hugo.im, 2026-10-16) — the open-source CLI framing, the read-only/no-network/well-under-a-second guarantees, and the author's own first-run anecdote (old plaintext git credentials file, token in the shell profile, passphrase-less SSH keys; cleanup took about twenty minutes) — https://hugo.im/posts/heldkeys
- The crates.io listing for heldkeys (versions 0.1.0, 0.1.1, 0.2.0, 0.3.0) — release cadence context — https://crates.io/crates/heldkeys
Star and release counts are reported as of the write date (2026-11-02) and will move — the project was 18 days old when this guide was written. The TanStack useHeldKeys hook is a different, unrelated product and is not covered here.
Frequently Asked Questions
Is Heldkeys free to use?
Yes. It is MIT-licensed open source, a single self-contained Rust binary with no telemetry, no paid tier, and no network calls. The GitHub Action form is equally free — pin it to a full commit SHA when it gates anything serious.
Can Heldkeys leak my secrets by printing them?
No — that is the core design constraint. It never prints a secret. Values appear only as fingerprints (at most the first 4 and last 3 characters), and only for known token formats whose prefix is public anyway, like AKIA or ghp_. Passwords and tokens of unknown shape get no fingerprint at all.
Does Heldkeys work on Windows?
Not yet. It is built and tested on Linux and macOS only. Under WSL, use the Linux binary. The Rust codebase compiles to static musl binaries for Linux (x86_64, aarch64) and native binaries for macOS (Intel, Apple Silicon).
Should I run Heldkeys in CI even if my workflows have no AI agents?
The workflow-analysis arc is agent-focused, but the runtime inventory (heldkeys ci) is useful for any CI job: it shows the env secrets, token, and OIDC capabilities the job holds — the answer to "if this job were compromised, what could it reach?" The --fail-on threshold makes it a gate.
How is Heldkeys different from gitleaks or TruffleHog?
Those answer "was a secret committed to this repo or its history?" Heldkeys answers "what is usable from here, right now?" — the runtime view. It never verifies against providers over the network and never prints secrets, so it complements rather than replaces repo scanners.
