Quick recap for those joining late. Spelunk started as a Go library to fetch secrets from any Source using a URI-like Coordinates system (intro). Then I added support for various cloud Secret Managers and a heap of Modifiers (v1.2.0 + v1.3.0). And after that I tore the repo apart into isolated modules/plugins, so you would only compile the third-party SDKs you actually needed (v2.0.0).

And now, time to tick another box!

Spelunk is a CLI now 💲

I ended the very first post with a little TODO list, and one of the points was: “Build a spelunk binary, so to embed it in shell scripts”. That’s what v2.2.* is about:

1
2
3
4
5
6
7
8
# dig up a secret and print it to STDOUT
$ spelunk [dig] "k8s://production/app-secret/db-password"

# checks if a secret exists: exit code 0/non-zero
$ spelunk exists "vault://secret/data/production/api-key"

# list which of the supported Sources is active, based on the credentials provided
$ spelunk creds -v

But what it is really made for, is embedding it into your scripts:

1
2
3
4
5
# dig bearer token from Kubernetes
$ curl -H "Authorization: Bearer $(spelunk k8s://ns/name/key)" https://api.example.com

# or a Docker Hub password from Vault
$ spelunk "vault://secret/data/ci/docker/password" | docker login --username user --password-stdin

The secrets are printed to STDOUT. And if you need to, increase log verbosity with one or more -v (STDERR).

Pipe | it wherever you need: I purposely made it as POSIX friendly as I could.

Features 🤩

It bundles almost every Source that Spelunk offers (aws://, az://, gcp://, vault://, k8s://, op://, kp://, plus the built-ins) and every Modifier (?jp=, ?yp=, ?tp=, ?xp=, ?b64*).

It auto-discovers credentials, by looking up idiomatic environment variables and the configuration files that providers usually expect. And if you need to set them explicitly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ spelunk --help

#...

AWS Secrets Manager Configuration (https://aws.amazon.com/secrets-manager/):
  --aws-region=STRING          AWS Region ($AWS_REGION).
  --aws-profile=STRING         AWS Profile ($AWS_PROFILE).
  --aws-endpoint-url=STRING    AWS Secrets Manager Endpoint URL ($AWS_ENDPOINT_URL_SECRETSMANAGER).

Azure Key Vault Configuration (https://azure.microsoft.com/en-us/products/key-vault/):
  --azure-vault-url=STRING            Azure Key Vault URL (e.g. https://<vault-name>.vault.azure.net) ($AZURE_KEYVAULT_URL).
  --azure-tenant-id=STRING            Azure Tenant ID ($AZURE_TENANT_ID).
  --azure-client-id=STRING            Azure Client ID ($AZURE_CLIENT_ID).
  --azure-client-secret=STRING        Azure Client Secret ($AZURE_CLIENT_SECRET).
  --azure-insecure-skip-tls-verify    Skip TLS verification for Azure Key Vault (useful for local emulators).

GCP Secrets Manager Configuration (https://cloud.google.com/security/products/secret-manager):
  --gcp-credentials-file=STRING    Path to GCP Service Account Credentials JSON file ($GOOGLE_APPLICATION_CREDENTIALS).

HashiCorp Vault Configuration (https://www.hashicorp.com/en/products/vault):
  --vault-addr=STRING         Vault Server Address (e.g. https://vault.example.com:8200) ($VAULT_ADDR).
  --vault-token=STRING        Vault Authentication Token ($VAULT_TOKEN).
  --vault-namespace=STRING    Vault Namespace ($VAULT_NAMESPACE).

Kubernetes Secrets Configuration (https://kubernetes.io/docs/concepts/configuration/secret/):
  --kubeconfig=STRING    Path to Kubeconfig file ($KUBECONFIG).

1Password Configuration (https://developer.1password.com/docs/cli/):
  --op-service-account-token=STRING    1Password Service Account Token ($OP_SERVICE_ACCOUNT_TOKEN).
  --op-integration-name="spelunk"      1Password Integration Name.
  --op-integration-version="dev"       1Password Integration Version.

Keeper Configuration (https://docs.keeper.io/en/enterprise-guide/commander-cli):
  --ksm-config=STRING    Keeper Secrets Manager configuration (Base64 string or file path) ($KSM_CONFIG).

It automagically adapts to the credentials it detects: the rest is politely ignored. But if you need to know what credentials it found:

1
2
3
# For example, on my system I only have Kubernetes right now
$ spelunk creds -v
2026-08-30T20:51:19+01:00 INF credentials valid plugin=k8s

Check out the CLI README, or go straight for spelunk --help. It is built on the superb alecthomas/kong, and so the usage instructions are extra nice.

Wait, why “almost” every Source? 🤨

Well, this is embarrassing.

You might have noticed that I apparently jumped from v2.0.x, to v2.2.x. I didn’t. I actually released v2.1.x, but I had not realised that it wouldn’t run on Windows. The binary required CGO to be built, because of the dependency on bitwarden/sdk-go for the bw:// Source. This caused me 2 issues:

  1. It required running 3 different goreleaser GitHub Workflows: a different runner per-OS, and a convoluted process to converge into a single release 😒
  2. It (somehow) has a runtime dependency on a Bitwarden platform-specific native library on Windows, so it didn’t actually run there 🧨

So, I decided that for now Spelunk CLI was good enough without Bitwarden. This allowed me to compile it without CGO, simplify the goreleaser process, and ship a fixed v2.2.x.

The Bitwarden plugin is still fully supported if you use Spelunk as a library. It just doesn’t get to ride in the CLI. Yet.

Installation, 4 ways 🖖

I provide statically linked binaries for OS=linux|darwin|windows and ARCH=amd64|arm64. They can be installed via:

  • From my Homebrew Tap (brew install detro/tap/spelunk)
  • Downloading the right pre-compiled binaries for your system
  • Compiled via go install github.com/detro/spelunk/cmd/spelunk@latest
  • Or by compiling a git clone https://github.com/detro/spelunk.git

Just head over here to get started.

And it comes with improvements too 🛠

  • JSONPath support is now built on ohler55/ojg, replacing an unmaintained dependency
  • Every module go.mod has been improved by declaring the lowest Go version it actually needs to be built, so nobody is forced to upgrade their toolchain because of me
  • Additional tests around the jp, yp and tp extractors

Everything is in the CHANGELOG (of course!). And the very latest release is currently v2.2.1.

Time to grab your pickaxe again, brew install, and go dig up some secrets from your terminal!