shh

command module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Mar 9, 2026 License: MIT Imports: 1 Imported by: 0

README

shh

Warning: Experimental — not yet ready for production use.

🤫 Commit your secrets. Yes, really.

  • Encrypted secrets live in your repo, safe to push, easy to share.
  • Add teammates by GitHub username — shh users add alice fetches their SSH key automatically.
  • No GitHub? Pass an age key directly.
  • One binary, one dependency (gh). Private keys stay in your OS keyring.

Install

brew install stefanpenner/tap/shh

Or from source:

go install github.com/stefanpenner/shh@latest

Quick Start

shh init                              # one-time setup (requires gh auth login)
shh set DATABASE_URL postgres://localhost/mydb
shh set API_KEY sk-secret123
shh shell                             # launch a shell with secrets loaded

No plaintext .env file ever touches disk.

Commands

shh set KEY value                     # add or update a secret
shh rm KEY                            # remove a secret
shh edit                              # edit all secrets in $EDITOR
shh list                              # list secret names
shh env                               # print export statements
shh shell                             # open a shell with secrets loaded
shh run -- <cmd> [args...]            # run a command with secrets injected
shh template <file>                   # render a template with secrets substituted
shh doctor                            # check your setup for common issues
shh whoami                            # show your key and identity

All commands default to .env.enc. Use -e to pick an environment:

shh shell -e staging                  # opens shell with staging.env.enc
shh run -e production -- node app.js  # runs with production secrets

Or pass a filename directly:

shh set KEY value staging.env.enc

Already have a .env file? Encrypt it:

shh encrypt .env                      # creates .env.enc (then delete .env)

Team Workflow

# You (project owner)
shh init
shh set SECRET supersecret
git add .env.enc && git push

# Add a teammate
shh users add alice
git add .env.enc && git push

# Alice (joining the project)
shh login                             # auto-detects SSH key via GitHub
shh shell                             # works immediately
shh users list                        # show who has access
shh users add <username-or-key>       # add by GitHub username or age key
shh users remove <user|#>            # revoke access (rotates data key)

CI / Production

Create a deploy key for environments that don't have a GitHub identity:

shh users add --name production-deploy
# Prints a secret key — store it as SHH_AGE_KEY in your CI/deploy platform
git add .env.enc && git push

Then in your CI pipeline or Dockerfile:

shh run -- node app.js            # secrets injected, SHH_AGE_KEY auto-filtered
# or
eval $(shh env -q)                # export secrets into the current shell

You manage one platform secret (SHH_AGE_KEY); everything else lives in .env.enc.

If you already have an age public key, pass it directly:

shh users add --name staging --key age1xyzt...

How It Works

shh uses envelope encryption: a random 32-byte AES-256 data key encrypts all secrets, and that data key is wrapped (age-encrypted) individually to each recipient.

  • Adding a user — re-wraps the existing data key for the new recipient. Secrets don't change.
  • Removing a user — generates a new data key, re-encrypts all secrets, wraps only for remaining recipients. The old key becomes useless.

Private keys stay in your OS keyring (macOS Keychain, GNOME/KDE Secret Service, Windows Credential Manager). Set SHH_AGE_KEY to override for CI/Docker. Set SHH_PLAINTEXT to point at a plain .env file to skip decryption entirely.

File Format

.env.enc is TOML:

version = 2
mac = "hmac-sha256-hex"

[recipients]
"https://github.com/alice" = "age1..."
"https://github.com/bob" = "age1..."

[wrapped_keys]
"https://github.com/alice" = "base64-data-key-wrapped-to-alice"
"https://github.com/bob" = "base64-data-key-wrapped-to-bob"

[secrets]
DATABASE_URL = "base64-aes-256-gcm-ciphertext"
API_KEY = "base64-aes-256-gcm-ciphertext"
Section Purpose
recipients Maps identities to age public keys
wrapped_keys The data key, individually wrapped to each recipient — one entry per recipient so branch additions don't conflict
secrets Values encrypted with AES-256-GCM using the data key; key name is authenticated data
mac HMAC-SHA256 over all fields, verified on every decrypt

Git Merge Support

Per-recipient wrapped keys mean adding teammates on different branches won't conflict. If a merge conflict does occur, any shh command auto-detects it, performs a semantic 3-way merge, and stages the result. True conflicts (same key modified both sides) are reported for manual resolution.

For proactive conflict prevention:

# .gitattributes (commit this)
*.env.enc merge=shh

# ~/.gitconfig (each developer)
[merge "shh"]
    name = shh encrypted env merge
    driver = shh merge %O %A %B

License

MIT

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
cli

Jump to

Keyboard shortcuts

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