Hort is live. A local secret store. Encrypted. Environment-aware. Built for AI agents. Open source.
GitHub: sebastian-breitzke/hort
Why I built this
I work with three AI coding agents daily — Claude Code, Codex, Gemini. All of them need credentials. API tokens, database passwords, tenant IDs, service URLs. Across environments. Across customers.
Here is how that works today: .env files. Or environment variables. Or hardcoded in some config somewhere. Or the agent asks you, you paste it, it forgets, it asks again next session.
None of this is discoverable. None of it is environment-aware. None of it is secure. And none of it works across agents.
Hort—treasure hoard—fixes that. One encrypted vault, one CLI, all agents.
How it works
You init a vault with a passphrase. Argon2id derives the encryption key. AES-256-GCM encrypts everything into a single file at ~/.hort/vault.enc. Unlock once — the session key stays cached until you lock it. No daemon, no background process.
From there it is straightforward:
# Store a secret
hort --set-secret grafana-token --value "glsa_..." --env prod --description "Grafana API token"
# Read it back — raw value on stdout, no decoration
TOKEN=$(hort --secret grafana-token --env prod)
curl -H "Authorization: Bearer $TOKEN" https://monitoring.example.com
Output goes to stdout with nothing else. No labels, no newlines, no formatting. Pipe-safe. Subshell-safe. Exactly what agents need.
Two dimensions: environment + context
This is where Hort gets interesting. Secrets are not just per-environment. They are per-environment and per-context.
A Kontext—context—is any second dimension you need. Tenant, customer, project. In my case: tenant IDs that differ per environment and per customer.
hort --set-secret tenant-id --value "default-123"
hort --set-secret tenant-id --value "prod-123" --env prod
hort --set-secret tenant-id --value "acme-prod-456" --env prod --context acme
hort --set-secret tenant-id --value "initech-prod-789" --env prod --context initech
hort --secret tenant-id --env prod --context acme # → acme-prod-456
hort --secret tenant-id --env prod # → prod-123
hort --secret tenant-id --env staging # → default-123 (fallback)
Fallback chain: env+context → env+* → *+*. Specific wins, baseline catches the rest.
Agent-first, not agent-compatible
The difference matters. Agent-compatible means “an agent could use it.” Agent-first means the entire interface was designed for agents from the start.
Hort’s --help output includes an AGENT INSTRUCTIONS section. When an agent runs hort --help, it learns the full interface — discovery flow, output format, error codes, do’s and don’ts. One call, complete understanding.
Discovery is built in:
hort --list
# secret tenant-id Tenant ID [env: *, prod] [ctx: acme, initech]
# config api-url Base API URL [env: *, prod]
hort --describe tenant-id
# Name: tenant-id
# Type: secret
# Description: Tenant ID
# Environments: *, prod
# Contexts: acme, initech
The agent sees “acme and initech are available contexts” and can decide which one to use — or ask you. --json flag gives machine-parseable output for agents that prefer structured data.
Exit code 2 means the vault is locked. The agent knows to ask you to run hort unlock instead of retrying or guessing.
Cross-platform, zero dependencies
Hort is a single Go binary. macOS, Linux, Windows. No runtime, no dependencies. Download, use.
# macOS
brew install sebastian-breitzke/tap/hort
# Or grab the binary from GitHub Releases
The vault file is portable. Same passphrase, same vault, different machine. hort init --restore on a new system — enter your passphrase, you are back in business.
What it is not
Hort is not HashiCorp Vault. It is not AWS Secrets Manager. It is not meant for production infrastructure or team-wide secret rotation.
It is a Werkbank—workbench—for developers who work with AI agents and need credentials accessible, discoverable, and secure on their local machine. The right tool for the right scale.
Security model
- AES-256-GCM authenticated encryption — tamper-proof
- Argon2id key derivation — memory-hard, GPU-resistant
- Session key: file permissions (chmod 600), OS user-scoped
- No network access, no telemetry, no cloud sync
- Threat model: protects against other OS users. Root access is game over — accepted for a local tool
Install
One line per OS. Downloads the binary, installs it, and starts vault setup:
macOS / Linux:
brew install sebastian-breitzke/tap/hort && hort init
macOS / Linux (without Homebrew):
VERSION=$(curl -fsSI -o /dev/null -w '%{redirect_url}' https://github.com/sebastian-breitzke/hort/releases/latest | grep -oE '[^/]+$') \
&& OS=$(uname -s | tr '[:upper:]' '[:lower:]') \
&& ARCH=$(uname -m | sed 's/x86_64/amd64/') \
&& curl -fsSL "https://github.com/sebastian-breitzke/hort/releases/download/${VERSION}/hort_${VERSION#v}_${OS}_${ARCH}.tar.gz" \
| tar -xz -C /tmp hort \
&& sudo mv /tmp/hort /usr/local/bin/hort \
&& hort init
Windows (PowerShell):
$release = (Invoke-RestMethod -Uri "https://api.github.com/repos/sebastian-breitzke/hort/releases/latest").tag_name
$version = $release.TrimStart("v")
$url = "https://github.com/sebastian-breitzke/hort/releases/download/$release/hort_${version}_windows_amd64.zip"
Invoke-WebRequest -Uri $url -OutFile "$env:TEMP\hort.zip"
Expand-Archive -Path "$env:TEMP\hort.zip" -DestinationPath "$env:LOCALAPPDATA\hort" -Force
$env:PATH += ";$env:LOCALAPPDATA\hort"
[Environment]::SetEnvironmentVariable("PATH", "$([Environment]::GetEnvironmentVariable('PATH', 'User'));$env:LOCALAPPDATA\hort", "User")
hort init
Set a passphrase, done. Vault created, ready to store secrets.
Try it
github.com/sebastian-breitzke/hort — MIT licensed, zero dependencies, works everywhere.
I am genuinely curious how other people handle the credentials-and-agents problem. If you have a setup that works — or one that drives you crazy — I would love to hear about it.