Help us shape the future of Go! Take the 2026 Go Developer Survey

geiger

module
v1.13.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: MIT

README

geiger

Is it still live, what does it reach, and how bad? Read-only blast-radius triage for leaked credentials.

release ci

Your secret scanner found a key but it won't tell you if it's still live or what it unlocks.

geiger does: pipe any credential-bearing text at it and it recognizes the credentials inside, runs read-only recon with each, and ranks what they actually reach by blast radius.

Triage: an incident responder's "how bad is this?" and a pentester's "what does this key reach?". Read-only by construction, dry-run by default.


Install

Binary — grab the archive for your OS/arch from Releases:

tar xzf geiger_*_linux_amd64.tar.gz && sudo mv geiger /usr/local/bin/

Source (Go 1.26+):

git clone https://github.com/puck-security/geiger && cd geiger
go build -o geiger ./cmd/geiger

Tutorial

geiger doesn't touch anything on the network until you say so. Dry-run first (default): it recognizes the credential and prints the read-only calls it would make. Try it on AWS's well-known example keys — no real secret needed:

printf 'AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE\nAWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n' | geiger

That prints the read-only calls it would run (sts:GetCallerIdentity, …). Add --live with a real credential to actually run them and get the impact note:

echo 'GITHUB_TOKEN=ghp_...' | geiger --live

How-to

# a file, stdin, or a cloud CLI's output
geiger --live .env
cat sso-cache.json | geiger --live
aws configure export-credentials | geiger --live

# the current environment
geiger --env --live

# this box's own cloud identity — harvest the instance-metadata credential
# (AWS instance role, GCP/Azure managed identity, k8s in-cluster SA, …) and
# triage what it reaches. The post-exploitation question, answered read-only.
geiger --metadata --live
geiger --metadata --live --intrusive   # + in-cluster k8s RBAC, secrets-store drain

# a whole repo / dir (walked; results sorted by impact)
geiger --live ./leaked-repo

# a scanner's report — e.g. a TruffleHog sweep of a compromised laptop,
# exactly what supply-chain worms (Shai-Hulud) run; triage which creds reach prod
geiger --live --from-trufflehog trufflehog.json
geiger --live --from-gitleaks gitleaks-report.json

# external recon: pipe a nuclei exposure scan straight in. Its templates pull the
# leaked value out of each exposed endpoint (/.env, phpinfo, instance metadata);
# geiger types, validates, and ranks it, and records the URL it leaked from. It
# also parses the response body when present, reassembling multi-field creds (an
# AWS key+secret pair, a connection string) the flat extracted-results can't —
# run nuclei with -irr to include the body.
# Stream over a pipe so live secrets never land on disk (add -o only if you must).
nuclei -t exposures/ -l targets.txt -j -irr | geiger --live --from-nuclei -

# rank by YOUR crown jewels (boost anything touching these to HIGH+)
geiger --live --context '1234567890,acme-prod,billing-service' ./repo

# self-hosted services need a host
echo 'VAULT_TOKEN=hvs....' | geiger --live --endpoint https://vault.internal:8200

# only what matters; save a clean artifact
geiger --live --min-severity high -o case-1234.txt ./repo

# OPSEC: identity call only; route egress through a proxy
geiger --live --min-footprint --proxy socks5://127.0.0.1:9050 .env

# machine-readable
geiger --live --json ./repo | jq .

Go deeper — --intrusive (read-only, but it leaves a trail). It connects to databases (Postgres, MySQL, MongoDB, Redis, SQL Server, Oracle, ClickHouse, Cassandra) with fixed queries, reads local SQLite and IDE stores in place, and calls cluster APIs. It follows secrets-store reads, draining Vault, Doppler, 1Password, and the cloud secret managers (AWS SM, GCP SM, Azure Key Vault) and triaging each value. It also redeems cached Azure and GCP refresh tokens to map their reach, which writes a sign-in to the audit log. This is the fan-out a worm performs, so you see the real blast radius. Plain --live stays read-only and never redeems a refresh token.

geiger --live --intrusive .env

Deeper, per service — --<service>-intrusive. Each major provider has a read-only deep tier. It shows what a leaked credential can do, not only what it can see. The flags table lists every flag. These tiers are separate from --intrusive because they are call-heavy and specific to one provider.

  • AWS, GCP, Azure map the IAM blast radius: privilege-escalation paths, assumable and cross-account roles, admin roles, and effective permissions.
  • Okta, Vault, GitHub, GitLab, Slack, Snowflake, Cloudflare size real reach: user-directory and SSO-app access, secret-engine mounts, CI/CD secret names, visible channels, reachable databases, and controlled domains.

Secret values are never printed; the CI-secret tiers return names only. AWS --aws-assume mints STS credentials for an SSO session, keeps them in memory, and scrubs them from output. The tiers that redeem a refresh token (GCP ADC, Azure) write a provider sign-in log.

geiger --live --aws-intrusive ~/.aws/credentials
geiger --live --aws-assume 123456789012/AdminRole ~/.aws/sso/cache
geiger --live --okta-intrusive --endpoint https://acme.okta.com .env

SSH keys — point it at a directory; it fingerprints each key (encrypted keys are locked, not dead). With --live it confirms the key's main use — git access — by attempting an SSH login to GitHub/GitLab/Bitbucket and reporting the account it authenticates as (or the repo, for a deploy key); a user key that logs in means pull/push to that account's private repos (supply-chain risk). --ssh-correlate adds candidate target hosts from ~/.ssh/config, known_hosts, and shell history.

geiger --live ~/.ssh            # fingerprint + test GitHub/GitLab/Bitbucket access
geiger --ssh-correlate ~/.ssh   # + guess other target hosts from local hints

Browser extensions — --browser models the impact of a malicious Chromium-family (Chrome, Edge, Brave, Chromium, Vivaldi) extension (CursedChrome-style proxy, infostealer, sideloaded MV3). It scores each installed extension's permission union — cookies + broad host access + request interception / script injection / proxy = read every site's session cookies and pivot through the browser — and flags sideloaded/unpacked ones (unsigned, not content-verified). With --live --intrusive it also inventories the live sessions such an extension would reach from the Cookies store metadata (domains only — the values are keychain-encrypted), ranked by blast radius (IdP/SSO sessions first).

Because an unsigned all-sites extension is capability-identical to a real CursedChrome, geiger doesn't guess intent — for each flagged sideloaded extension it builds a responder triage bundle: install age, UI surface, dev-project markers, the extension id, and a low-false-positive grep of its on-disk code (and, under --intrusive, its LevelDB storage) for hardcoded remote hosts (websocket / public-IP endpoints) to verify against egress/DNS logs. Everything is emitted as clean IOCs in --json (detail arrays) for a SIEM.

By default only extensions with risky reach are shown; narrow/benign ones are collapsed into a count. --all lists every installed extension (a full inventory).

geiger --browser                    # score extensions + triage bundle
geiger --browser --live --intrusive # + session inventory + storage IOC grep
geiger --browser --all              # full inventory (incl. narrow/benign)
geiger --browser --json             # IOCs (extension id, hosts) for a SIEM

Agentic reach — point geiger at an agent config, a repo, or a home directory and it maps what the agent's tool chain reaches. This is on by default: no flag, it just happens when a config turns up in the walk.

The credential axis and the reach axis are separate, and that is the point. A config whose every secret is stored properly — OS env, a keychain, an OAuth flow — can still wire the agent to a filesystem server rooted at /, a shell server, and the corporate wiki. Perfect credential hygiene bounds none of that.

geiger ~/.claude.json                    # what this agent's tools reach
geiger ./repo                            # .mcp.json / .cursor / .vscode in a repo
geiger --live ~/.cursor/mcp.json         # enumerate remote servers' real tool lists
geiger --live --spawn-stdio ~/.claude    # + run local stdio servers to enumerate them

Each server is typed into reach primitives — exec, corpus-search, secrets-read, code-write, cloud-control, fs-read/fs-write with their root path, data-read, net-egress, untrusted-in, destructive, identity-admin. Bulk store search is deliberately its own primitive: "give me every credential in Confluence" is one tool call and the highest-yield move in a real engagement, so folding it into a generic "reads data" would bury it.

That list is inventory, and it does not set a tier. A config file says what an agent is wired to; it cannot say whether that is appropriate for the machine it is on, and a filesystem server scoped to a project directory is the normal setup. An ordinary developer laptop scores INFO.

The tier comes from the compositions — reach that exists only because the tools share one context window, which no per-component scanner can see:

Chain Why it matters
lethal trifecta untrusted content, private data and a way out in one context — one poisoned page and the agent leaks what it can read
bulk read plus a way out search the store, send the results. Two tool calls, no exploit
exec on this host an exec tool makes the agent's reach the host's reach: every credential on the box, geiger's other findings in the same run included
reads a secret store tool-chain access becomes credentials that outlive the session
shared context a low-trust server sharing a context with a high-reach one
unpinned package npx -y / uvx refetch at every launch, so the code that runs tomorrow need not be the code in the config

Alongside those, three things the file itself establishes: a filesystem root that is the whole disk or home directory rather than a project, no approval prompt (permissions.allow, alwaysAllow, YOLO mode), scored as a property of the whole surface because it removes the human from every chain at once, and — under --live only — a server that answers with no credential (enumeration sends none, so the token in the config is not what gates it) or is reached over plaintext. Hooks are inventoried too: they run shell on lifecycle events with no model and no approval in that path, and nothing else lists them.

Runtimes read: Claude Code and Claude Desktop, Cursor, VS Code, Windsurf, Cline/Roo/Kilo, Continue, Gemini CLI, Zed, Codex, Goose.

Honest by construction. The note always says where the reach came from: read from the config (what these packages are known to do) or reported by the servers themselves. A surface whose servers type to nothing at all reads UNKNOWN rather than getting a severity invented from a guess.

The two flags split by transport. --live enumerates the remote servers' real tool lists over http (tools/list, read-only; tools/call is never issued). A local stdio server is only asked under --spawn-stdio, because asking it means running the command in the config — a separate flag from --intrusive on purpose: executing an argv that came out of the scanned file is worse than anything --intrusive permits. Each server line says which it is (stdio <command> or a URL) and whether it was asked.

See docs/design/agentic-reach.md for the design.

Redact what geiger found — once you have rotated (or decided to rotate) what a scan turned up, geiger can strip the values from the files it read, so a backup directory or an old dotfile stops leaking the same credentials again:

geiger --dangerous-redact ~/.claude                    # plan only: what would change
geiger --dangerous-redact --confirm-redact ~/.claude   # rewrite in place
geiger --dangerous-redact --confirm-redact --git-history \
       --redact-replacements /tmp/repl.txt ./repo      # + file for git filter-repo
git filter-repo --replace-text /tmp/repl.txt           # then scrub the history

The plan lists each file, and under it each value: the line it sits on, the module that recognized it, the variable or JSON path it came from, a masked tail to match against the findings report, and the form it was found in. The same list is printed after a rewrite, as the record of what changed. A plain or JSON-escaped value is a text swap: the file keeps its shape and only the value changes. A value stored as base64 (a docker auth blob, a kubeconfig key) is replaced too, but the placeholder does not decode, so the consumer of that file errors until the value is replaced. Anything else is listed under a warning with the reason and is not rewritten: git history blobs (use the replacements file), archive members, symlinks, binary and SQLite stores, values harvested from a secrets store, and values that were transformed before geiger saw them.

Two things it will not do. It keeps no backup: a backup would hold the secrets again. And it revokes nothing: a redacted file is one that stops re-leaking, not a rotated credential. It refuses to run on stdin, --env, --metadata, --browser, or a scanner report, since none of those is a file it read itself.


Where geiger fits

geiger layers on top of the scanners you already run: detection finds the secret, geiger characterizes it. Pipe in a report (--from-gitleaks / --from-trufflehog / --from-nuclei -), or point it at a directory — which is how IR usually uses it.

gitleaks TruffleHog GitGuardian Kingfisher geiger
Find secrets in code / git / files ✅ ✅ ✅ ✅ walks a tree; consumes their reports
Verify the secret is live — ✅ ✅ ✅ ✅
Characterize blast radius (identity, scope, reach) — partial partial ✅ ✅ ~170+ types
Triage any credential you hand it, type auto-detected — — — partial ✅
Map an agent's tool-chain reach + escalation chains (MCP) — — — — ✅
Drain secret-managers + recursively triage downstream — — — — ✅
DB / cluster / on-disk store recon (read-only) — — — partial ✅
Ranked for IR ("how bad, in what order") — — partial ✅ ✅
Local, no SaaS / account ✅ ✅ — (SaaS) ✅ ✅

Performance

Offline triage runs locally in seconds. A live run is dominated by provider round-trip, so it scales with --concurrency and rate limits, not corpus size.

measured
Dry-run, 20k files / 148MB ~2.9s warm, ~8s cold
Dry-run, a few files 20–30ms
Calls per credential identity + small inventory fan-out (AWS key: 4)
--min-footprint 1 call

--intrusive is slower due to DB handshakes, secret-manager fan-out, recursive triage.


Reference

Flags

Flag Effect
(stdin / files / dirs) input source; multiple files/dirs may be passed, and a directory is walked
--live make read-only recon calls (default: dry-run)
--intrusive connect to DBs / cluster APIs, read local stores, harvest downstream secrets (needs --live)
--min-footprint identity call only; skip inventory fan-out
--env read current environment variables
--metadata harvest this instance's metadata credential (AWS/GCP/Azure/k8s/Alibaba/DigitalOcean/OCI) and triage it; requires --live (it's a network read)
--spawn-stdio enumerate local stdio MCP servers by running each configured command (needs --live); remote servers are enumerated by --live alone
--aws-intrusive deep AWS IAM reads: full account authorization graph, self-grant unroll (groups + attached/inline policies), and role trust graph (needs --live; heavy CloudTrail)
--aws-assume SPEC for an AWS SSO session, mint STS credentials and run the full key characterization per target: all, ACCOUNT, ACCOUNT/ROLE, or a comma-separated list (implies --aws-intrusive; needs --live)
--slack-intrusive deeper Slack reach: workspace, users, channels, and files visible to the token (needs --live; the token's scopes are reported without it)
--github-intrusive deeper GitHub reach: org member counts, org and repo Actions secret names, and installation-token repo reach (needs --live)
--gitlab-intrusive deeper GitLab reach: project and group counts, maintainer/owner reach, and CI/CD variable keys (needs --live)
--gcp-intrusive deeper GCP reach: probe effective IAM permissions (privilege escalation, code execution, data access) across reachable projects; redeems a gcloud ADC refresh token (needs --live)
--azure-intrusive deeper Azure reach: Entra directory roles and Azure RBAC role assignments across subscriptions; redeems the az refresh token to mint Graph/ARM tokens; also probes an Entra service principal's app permissions (needs --live)
--okta-intrusive deeper Okta reach: user directory read, SSO app inventory, and an admin-access probe (needs --live)
--vault-intrusive deeper Vault reach: secret-engine mount inventory and path-capability probe (needs --live)
--snowflake-intrusive deeper Snowflake reach: reachable databases and the roles the user holds (needs --live)
--cloudflare-intrusive deeper Cloudflare reach: zone/domain names and Workers/R2 access (needs --live)
--browser model malicious-browser-extension impact: score installed Chromium-family extensions; with --live --intrusive, inventory the live sessions they'd reach
--all with --browser, list every extension (not just the risky ones)
--endpoint URL host/instance for self-hosted & set-shaped creds
--proxy URL route HTTP recon via http/https/socks5 proxy
--timeout DUR per-credential recon timeout (default 15s)
--concurrency N credentials reconned at once on --live (default 8)
--context TERMS comma-separated crown-jewel terms; a match raises tier
--min-severity TIER only print findings at or above a tier (critical/high/medium/low/info/unknown/dead); dead is the floor, so info excludes dead and unknown, and high keeps only critical+high
-o, --output FILE write results to FILE instead of stdout (0600, color off; status stays on stderr)
--json machine-readable output (NDJSON, one note per line)
--sarif SARIF 2.1.0 output for code-scanning and triage viewers; tier and score ride in properties (NDJSON stays canonical)
--stream print results as found (discovery order) instead of sorted by impact
--no-reverse keep highest-impact findings first; by default an interactive terminal reverses them to the bottom (above the summary) so the worst don't scroll off the top
--only TYPES / --skip TYPES scope by module name or category (databases,cloud,secrets,ai,vcs,kubernetes,identity,backup,endpoint)
--from-gitleaks F / --from-trufflehog F triage each finding in a scanner report
--from-nuclei F triage each value extracted by a nuclei JSONL (-j) scan; F = - reads stdin (stream over a pipe)
--from-kingfisher F triage each finding in a Kingfisher JSON/JSONL report (not --redacted — geiger needs the value); F = - reads stdin. Their finding fingerprint is carried through to --json/--sarif so their viewer dedupes against its own findings
--dangerous-redact plan an in-place rewrite of the scanned files that replaces each found credential with REDACTED-BY-GEIGER; prints the plan, writes nothing. See Redact what geiger found
--confirm-redact with --dangerous-redact, do the rewrite. No backup, no undo
--redact-replacements FILE with --confirm-redact, also write a git filter-repo --replace-text file (0600) for scrubbing git history
--git-history also scan blobs in a repository's git history — catches credentials deleted from the working tree but still recoverable from the repo (needs git on PATH)
--ssh-correlate SSH: read local hints for candidate target hosts
--trace print the raw request + response of each call (secrets masked)
--user-agent UA User-Agent for recon calls (default geiger/<version>)
--color MODE auto (default, off when piped) / always / never
-v / -q show planned/executed calls (and full finding detail) / quiet stderr
--version print version

Tiers

CRITICAL · HIGH · MEDIUM · LOW · INFO · DEAD — a composite blast-radius score (capability × reach × sensitivity), relative not absolute. --context matches and force-multiplier capabilities force at least HIGH.

What geiger reads — and what it can't

geiger triages a credential you were handed, or one sitting on disk.

  • In scope — on-disk / offline-readable. API tokens, connection strings, cloud CLI caches (~/.aws, gcloud, MSAL), SSH keys, kubeconfigs, secrets-manager creds, MCP configs (also triaged for tool-chain reach, not just the secrets in them — see Agentic reach), AI-IDE plaintext token stores, password-manager recovery material (KeePass, encrypted Bitwarden — offline-crackable with the master password), plaintext exports, and Firefox saved logins (logins.json + key4.db), which decrypt offline when no primary password is set.
  • Out of scope — in-process / OS-bound. Chromium passwords & cookies (wrapped by DPAPI / macOS Keychain / Secret Service), raw DPAPI blobs, the macOS Keychain, LSASS. Reading those means decrypting against a live OS session — credential extraction from a host, not triage of one. Not always a black and white line. (--browser models the extension-capability and session-metadata angle — which domains have a live session, which extension could reach them — without decrypting the cookie values; see Browser extensions above.)

Coverage

Recognition rides on gitleaks (shape/checksum) plus geiger's own shape/env-name recognizers; an unrecognized type is reported unknown, not characterized. Triage keys on capability — a key that runs code, wipes devices, restores backups, or reads other secrets is a force multiplier; a billed-usage API key is a warning.

Full coverage — 178 credential types (regenerate with go run ./tools/coverage)

Cloud & hosting

Credential / app Reach
aws AWS account — IAM-scoped access across all AWS services
aws_sso active SSO session
aws_sso_registration SSO client registration (no session)
gcp_service_account GCP service account — exchanged a read-only token
gcp_adc gcloud user credentials — delegated user access
gcp_metadata GCP instance service account — token-scoped reach
google_oauth_client Google OAuth client secret — valid; redeems this app's tokens
azure_msal Azure CLI session — Entra identity, refreshable headlessly
alibaba Alibaba Cloud RAM credential
oci_instance_principal OCI instance principal
digitalocean DigitalOcean — droplets, databases, Spaces, Kubernetes
digitalocean_oauth DigitalOcean OAuth — account API access
linode Linode — compute, storage, DNS account control
cloudflare Cloudflare — DNS/zones/Workers per token scope
cloudflare_global Cloudflare Global API Key — full account control
fastly Fastly — CDN config, edge purge/secrets
heroku Heroku — app deploy + config-vars (downstream secrets)
render Render — deploy (code exec) + env-secret access
railway Railway — deploy (code exec) + project-variable access
flyio Fly.io — machine deploy (code exec) + app-secret access
netlify Netlify — site deploy + build env vars
vercel Vercel — project deploy + env vars
tailscale Tailscale API token — tailnet device/ACL admin + auth-key minting
tailscale_oauth_client Tailscale OAuth client (non-expiring) — tailnet device/ACL admin + auth-key minting
tailscale_auth_key Tailscale auth key — enrolls a device into the tailnet; not verifiable read-only
terraform_cloud Terraform Cloud — workspace state & variables (often secrets)
oci_config Oracle Cloud — API signing config (key_file referenced, not inline)

Source control & packages

Credential / app Reach
github_pat GitHub — repo read/write/admin, org membership, Actions
gitlab GitLab — repo/project access per token scope
gitlab_ci_token GitLab CI job token — pipeline-scoped repo/registry access
jfrog JFrog Artifactory — artifact read/write + package admin
docker_registry container registry — pull/push images (supply-chain)
npm npm — publish/yank packages (supply-chain)
pypi PyPI — publish packages (supply-chain)
rubygems RubyGems — publish gems (supply-chain)

CI/CD & build

Credential / app Reach
buildkite Buildkite — pipelines + build-agent access
circleci CircleCI — pipelines + project env vars

Media & file services

Credential / app Reach
filestack Filestack API key — file upload/transform on this account

Databases & data platforms

Credential / app Reach
db_connection_string direct DB data-plane (pg/mysql/mongo/redis/mssql/oracle/clickhouse/cassandra/sqlite)
snowflake Snowflake — data-warehouse access (account-wide at ACCOUNTADMIN)
databricks Databricks — workspace, jobs, notebooks, data
mongodb_atlas MongoDB Atlas service account — Admin API access to orgs/projects/clusters
supabase Supabase service_role — RLS-bypass full DB + auth admin
planetscale PlanetScale service token — database & branch admin
neon Neon API key — Postgres project & role admin
aiven Aiven token — managed-service & credential admin
upstash Upstash management API — database & token admin
redis_cloud Redis Cloud API — subscription & database admin
clickhouse_cloud ClickHouse Cloud API key — service & member control
clickhouse_selfhosted ClickHouse (self-hosted) — SQL data & user access
plaid Plaid keys — bank-account data access (financial PII)

AI / LLM & agentic

Credential / app Reach
openai OpenAI — model access, files/fine-tunes; org-owner = key/billing admin
anthropic Anthropic — Claude model access (billed)
claude_code_oauth Claude Code / Claude subscription OAuth token — acts as the signed-in user
gemini Google Gemini — generative API (billed usage)
azure_openai Azure OpenAI — resource model access (billed usage)
cohere Cohere — LLM API access (billed)
mistral Mistral — LLM API access (billed)
replicate Replicate — model runs (billed) + model access
huggingface Hugging Face — model/dataset access; write token can push
groq groq — LLM API access (billed usage)
together together — LLM API access (billed usage)
deepseek deepseek — LLM API access (billed usage)
openrouter openrouter — LLM API access (billed usage)
xai xai — LLM API access (billed usage)
fireworks fireworks — LLM API access (billed usage)
perplexity Perplexity — LLM API (billed usage)
elevenlabs ElevenLabs — voice API (billed usage + voice library)
stability Stability AI — image API (billed usage)
pinecone Pinecone — vector index read/write (embedded data)
bedrock Amazon Bedrock API key — foundation-model access (billable)
mcp_config agent tool chain — what the agent reaches through its MCP servers, hooks, and approval posture
ai_ide_store AI-IDE token store (plaintext SQLite)
projectdiscovery_neo Neo key — reads this account's assessments and can launch new ones

Secrets managers & vaults

Credential / app Reach
vault HashiCorp Vault — read secrets per policy
conjur CyberArk Conjur — secrets-manager access (reads downstream secret values)
cyberark_pvwa CyberArk PVWA — privileged-account vault access
infisical Infisical — secret-store read across authorized projects
akeyless Akeyless — secret-store read (list-items / get-secret-value)
delinea_secret_server Delinea/Thycotic Secret Server — vaulted-secret read access
doppler Doppler — project config/secret access
onepassword_connect 1Password Connect — vault item access via the Connect server
onepassword_sa 1Password service account — vault secret access (CLI/SDK only)
onepassword_secret_key 1Password Secret Key — vault-unlock half; dangerous with the master password
bitwarden Bitwarden API key — valid; enumerates the vault (items stay encrypted without the master password)
bitwarden_vault Bitwarden encrypted vault — offline-crackable with the master password
keepass_db KeePass vault — offline-crackable to every secret with the master password
vault_export_plaintext plaintext credential dump — full account takeover across every listed site

Identity, SSO & directory

Credential / app Reach
okta Okta — directory, SSO apps, admin per scope
pingone PingOne — identity & SSO admin
pingfederate PingFederate admin API — OAuth client & federation-trust control
entra_sp Microsoft Entra service principal — Graph/Azure per app roles
jumpcloud JumpCloud — directory, device & SSO admin
sailpoint SailPoint — identity governance (certs, provisioning)
auth0 Auth0 — tenant management API (users, apps, rules)
duo Duo — MFA admin API (bypass codes, user mgmt)
workday Workday — HR/finance records (PII)
workos WorkOS API key — SSO/Directory Sync/User Management control

Endpoint, MDM, RMM & config-mgmt

Credential / app Reach
ninjaone NinjaOne RMM — script execution + remote control across endpoints
atera Atera RMM/PSA — full agent & customer inventory
kandji Kandji MDM — device inventory + remote lock/erase
jamf Jamf Pro — script execution + MDM lock/wipe
mosyle Mosyle MDM — device control incl. remote wipe/lock
automox Automox — patch policies + worklet script execution across endpoints
tanium Tanium — package deploy + action execution across endpoints
ansible_awx Ansible AWX/Tower — playbook execution across managed hosts
puppet_enterprise Puppet Enterprise — orchestrator task/plan execution on nodes
saltstack SaltStack salt-api — arbitrary module execution across minions
fleet Fleet (osquery) — live queries + script execution + MDM wipe

Monitoring & observability

Credential / app Reach
datadog Datadog — metrics/logs/APM + monitor & key admin
newrelic New Relic — telemetry + account admin
grafana Grafana — dashboards/datasources; server-admin = full
splunk Splunk — search over all data + scripted-input command execution
sumologic Sumo Logic — log search (secrets/PII) + collector admin
dynatrace Dynatrace — observability data + config API
honeycomb Honeycomb — event data + query API
sentry Sentry — error events (may hold secrets/PII) + org admin
zabbix Zabbix — global script execution on monitored hosts
auvik Auvik — network topology & device inventory
manageengine_opmanager ManageEngine OpManager — device management + workflow script execution
lacework Lacework — cloud security posture across connected accounts
wiz Wiz — cloud security graph (findings, identities, secrets)
snyk Snyk — code/dependency vuln data + org project access

Backup & DR

Credential / app Reach
veeam Veeam Backup & Replication — job control + restores
acronis Acronis Cyber Protect — backup/restore + agent control
cohesity Cohesity — protection-job & restore control
netbackup Veritas NetBackup — policy & restore control
commvault Commvault — backup/restore + client management

ITSM, productivity & support

Credential / app Reach
servicenow ServiceNow — ITSM/CMDB records + workflows
jira Jira (Atlassian Cloud) — full issue/project read + user-directory PII
ivanti Ivanti ITSM — incident/CMDB access + employee PII
snipeit Snipe-IT — asset/license inventory + user PII
pagerduty PagerDuty — on-call schedules + incident API
linear Linear — issues/projects + member data
asana Asana — tasks/projects + workspace members
notion Notion — workspace pages/databases (often secrets/PII)
zendesk Zendesk — support tickets (customer PII)
intercom Intercom — conversations & customer profiles (PII)
freshservice Freshservice ITSM key — tickets, requester PII and CMDB
freshdesk Freshdesk help desk key — tickets and contact PII
freshchat Freshchat — all conversations and contact PII
freshsales Freshsales CRM — contact PII and pipeline data
atlassian Atlassian API token — set email + site to validate reach
confluence Confluence (Atlassian Cloud) — full space/page read; pages often hold secrets

Comms, email & SMS

Credential / app Reach
slack Slack — messages, files, users per token scope
discord_bot Discord bot — guild/message access per intents
telegram_bot Telegram bot — send/read in its chats
zoom Zoom — meetings, recordings, user admin (S2S OAuth)
twilio Twilio — SMS/voice send (billed) + message logs (PII)
vonage Vonage — SMS/voice send (billed)
sendgrid SendGrid — email send + template/contact access
mailgun Mailgun — email send + logs (recipient PII)
mailchimp Mailchimp — audience export (subscriber PII) + send
postmark Postmark — transactional email send + history
brevo Brevo — email/SMS + contact PII

Payments, CRM & SaaS data

Credential / app Reach
stripe Stripe — charges/customers/payouts (a live key moves money)
square Square — payments + customer data (financial)
coinbase Coinbase — account access (financial; can move funds)
salesforce Salesforce — CRM object access (customer PII)
hubspot HubSpot — CRM contacts/deals (PII)
shopify Shopify — store orders/customers (PII) + admin
klaviyo Klaviyo — customer-profile export (PII) + campaign send
braze Braze — message-send to all users + profile export (PII)
segment Segment — customer event-pipeline access (behavioral PII)
mixpanel Mixpanel — product-analytics data (behavioral PII)
amplitude Amplitude — product-analytics data (behavioral PII)
customerio Customer.io — messaging + customer data (PII)
docusign DocuSign — envelopes/agreements (legal docs)
dropbox Dropbox — file access per scope
box Box — file/folder access; admin = all content
airtable Airtable — base data (often PII/secrets)
algolia Algolia — search index read/write; admin key = full
confluent Confluent Cloud — Kafka cluster & topic admin

Local credential stores & keys

Credential / app Reach
ssh_private_key SSH private key — confirmed git host access
kubeconfig kubeconfig — cluster credential
firefox_logins Firefox saved logins — offline-decryptable on-disk store
jwt decoded offline — no network call made; map issuer to its provider for live recon
generic_secret unrecognized credential (matched by name)
needs_endpoint recognized — provide --endpoint to characterize

Output

A block per credential: a tier, a redacted title with the source location, never the raw secret, labeled findings (⚠ notable, ⚠⚠ force multiplier, ? can't-determine-read-only), and a one-line takeaway.

For IR, each finding leads with where and when: an exposure line classifies the source — a crash dump (in-memory, persisted to disk, often auto-uploaded — may have left the host), a VS Code local-history snapshot, an IDE secret store, shell history, a log — and source modified / validated live carry the file mtime and live-check timestamp. When a secret turns up in several files, also exposed in groups them by class (8 local-history snapshots; 7 crash dumps) instead of listing paths (the full list expands under -v and in --json).

With -v each planned call prints as a copy-pasteable curl. Triaging more than one credential prints a closing summary — tier breakdown, rotate-first queue, and follow-ups (secrets-store reach, what couldn't be characterized, anything hidden by --min-severity). GitHub write/admin and org-admin are read from each repo's permissions and /user/memberships/orgs, so they're reported even for fine-grained PATs that expose no scopes.

Drift resilience. Beyond declared field paths, every response is scanned heuristically (admin/owner indicators → force multiplier; a fallback identity + count when the API shape changed), so a module stays useful as providers rename fields. --trace shows the raw request/response (secrets masked).


How it works

Pipeline: recognize → (authenticate) → recon → note. Recon runs the identity/whoami call first, then a couple of count calls to size reach.

Safety model

  • Read-only by construction. One client allows only GET/HEAD plus a short allowlist of read-only POSTs (STS GetCallerIdentity, k8s SelfSubjectRulesReview, the single OAuth token exchange). DB recon uses a read-only session and a fixed query allowlist. Local stores (SQLite, IDE state.vscdb, Firefox key4.db) open read-only. A guard test enforces this across every module.
  • Dry-run by default. --live is required and always prints the destinations it hits (real provider APIs, and their audit logs).
  • Attribution. Recon identifies itself as geiger/<version> — dual use beware, no detection evasion; defenders can attribute the calls.
  • Secrets are not printed or stored. Redacted everywhere; scrubbed from URLs, headers, and errors.
  • Untrusted-input hardening. Refuses cloud-metadata targets (SSRF), sanitizes hostile API responses, strips file-referencing DSN options
  • Endpoint provenance. A host read out of scanned data is untrusted: a planted URL would otherwise aim a real credential at whoever planted it. Every module declares where its credential may go, enforced centrally at recognition time — a SaaS module is pinned to its vendor's domains, --endpoint outranks anything in the file, and a violation degrades to "needs endpoint" instead of dialing. Self-hosted services can legitimately live at any domain, so those are not pinned; their destination is flagged in the note instead.

Principle: likely impact, not perfect impact. geiger is triage, not deep cloud-privesc graphing (use PMapper/CloudFox/ScoutSuite/etc for that).

Authorized use only. geiger exercises live credentials — run it only on creds you are entitled to triage.


Contributing a module

Most providers are a few lines of declarative recipe:

add("digitalocean-pat", recipe.HTTP{
    ModuleName: "digitalocean", Base: "https://api.digitalocean.com",
    Auth:   recipe.AuthSpec{Kind: recipe.Bearer},
    Whoami: recipe.GET("/v2/account").Field("email", "account.email"),
    Calls:  []recipe.Call{recipe.GET("/v2/droplets").CountFrom("meta.total", "droplets")},
}.Module())

If your Base is templated on a host — {endpoint}, {host}, {api}, {server} — you must also declare an Endpoint policy. That host comes from the file being scanned, which an attacker may have written, so geiger needs to know which hosts are legitimate for your service:

    // SaaS-only: pin the vendor's domains (list every region and gov host).
    ModuleName: "zendesk", Endpoint: saasOnly("zendesk.com"), Base: "{endpoint}",

    // Deployable at any domain — including vendors shipping both SaaS and
    // on-prem, where pinning would break real deployments.
    ModuleName: "vault", Endpoint: selfHosted, Base: "{endpoint}",

Resolve the host with resolveEndpoint rather than reading a variable yourself: it puts the operator's --endpoint ahead of anything in the file. Don't pair a service's credential with another service's URL variable — bind each host variable to the module it names. TestEveryEndpointSteeredModuleDeclaresAPolicy fails if a module skips this.

Add an httptest-backed test, then go run ./tools/coverage to refresh the coverage table above. Exotic signing (SigV4, RS256-JWT, Digest) implements the module.Module interface directly with the internal/sign + internal/auth helpers — see internal/modules/ for examples.

Directories

Path Synopsis
cmd
geiger command
Command geiger triages leaked credentials: it recognizes the credentials in piped text, a file, the environment, a directory, or a scanner report, runs read-only recon with each, and prints a short note on what the credential is and what it can reach.
Command geiger triages leaked credentials: it recognizes the credentials in piped text, a file, the environment, a directory, or a scanner report, runs read-only recon with each, and prints a short note on what the credential is and what it can reach.
internal
agent
Package agent triages the reach of an agentic system's tool chain.
Package agent triages the reach of an agentic system's tool chain.
auth
Package auth implements the four headless OAuth grants Geiger supports.
Package auth implements the four headless OAuth grants Geiger supports.
browser
Package browser models the impact of a malicious Chromium browser extension (CursedChrome-style browser proxy, infostealer, sideloaded MV3 extension).
Package browser models the impact of a malicious Chromium browser extension (CursedChrome-style browser proxy, infostealer, sideloaded MV3 extension).
color
Package color provides terminal coloring that is a no-op unless enabled, so output stays clean when piped or redirected (codes would otherwise pollute files and tools like jq).
Package color provides terminal coloring that is a no-op unless enabled, so output stays clean when piped or redirected (codes would otherwise pollute files and tools like jq).
dbrecon
Package dbrecon runs read-only data-plane recon against a database connection string.
Package dbrecon runs read-only data-plane recon against a database connection string.
imds
Package imds harvests credentials from cloud instance-metadata services (AWS IMDS, GCP/Azure metadata, Kubernetes in-cluster SA, Alibaba, DigitalOcean, OCI) and normalizes each into a synthetic dotenv blob that geiger's normal recognizers pick up.
Package imds harvests credentials from cloud instance-metadata services (AWS IMDS, GCP/Azure metadata, Kubernetes in-cluster SA, Alibaba, DigitalOcean, OCI) and normalizes each into a synthetic dotenv blob that geiger's normal recognizers pick up.
module
Package module defines Geiger's core domain types and the Module contract.
Package module defines Geiger's core domain types and the Module contract.
module/recipe
Package recipe builds declarative HTTP modules.
Package recipe builds declarative HTTP modules.
modules
Package modules is the credential catalog.
Package modules is the credential catalog.
note
Package note renders a module's Note for the terminal or as JSON.
Package note renders a module's Note for the terminal or as JSON.
parse
Package parse turns raw input (a file, stdin, or the environment) into a Blob: the original text plus a flattened key/value view and any structured form (JSON object, INI sections).
Package parse turns raw input (a file, stdin, or the environment) into a Blob: the original text plus a flattened key/value view and any structured form (JSON object, INI sections).
pipeline
Package pipeline wires the stages together: parsed blob → recognize → for each match (authenticate → recon → summarize) → notes.
Package pipeline wires the stages together: parsed blob → recognize → for each match (authenticate → recon → summarize) → notes.
recognize
Package recognize routes parsed input to modules.
Package recognize routes parsed input to modules.
recon
Package recon provides the single HTTP client every module uses for recon.
Package recon provides the single HTTP client every module uses for recon.
redact
Package redact masks secret material so it never appears in Geiger output.
Package redact masks secret material so it never appears in Geiger output.
score
Package score derives a relative blast-radius score and tier for a Note.
Package score derives a relative blast-radius score and tier for a Note.
sign
Package sign holds the request/assertion signing schemes Geiger modules need: RS256 JWT assertions (GCP, Salesforce, Snowflake), Azure SharedKey, HTTP Digest (MongoDB Atlas), and generic HMAC.
Package sign holds the request/assertion signing schemes Geiger modules need: RS256 JWT assertions (GCP, Salesforce, Snowflake), Azure SharedKey, HTTP Digest (MongoDB Atlas), and generic HMAC.
tools
coverage command
Command coverage regenerates the credential-coverage table for the README from the live module registry.
Command coverage regenerates the credential-coverage table for the README from the live module registry.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL