- Rename 'unlockers' command to 'unlocker' for consistency - Move all unlocker subcommands (list, add, remove) under single 'unlocker' command - Add --quiet/-q flag to 'secret list' for scripting support - Update documentation and tests to reflect command changes The quiet flag outputs only secret names without headers or formatting, making it ideal for shell script usage like: secret get $(secret list -q | head -1) |
||
---|---|---|
cmd/secret | ||
internal | ||
pkg | ||
.cursorrules | ||
.dockerignore | ||
.gitignore | ||
.golangci.yml | ||
AGENTS.md | ||
CLAUDE.md | ||
coverage.out | ||
Dockerfile | ||
go.mod | ||
go.sum | ||
LICENSE | ||
Makefile | ||
README.md | ||
TODO.md |
Secret - Hierarchical Secret Manager
Secret is a command-line secret manager that implements a hierarchical key architecture for storing and managing sensitive data. It supports multiple vaults, various unlock mechanisms, and provides secure storage using the Age encryption library.
Core Architecture
Three-Layer Key Hierarchy
Secret implements a three-layer key architecture:
- Long-term Keys: Derived from BIP39 mnemonic phrases, these provide the foundation for all encryption
- Unlockers: Short-term keys that encrypt the long-term keys, supporting multiple authentication methods
- Version-specific Keys: Per-version keys that encrypt individual secret values
Version Management
Each secret maintains a history of versions, with each version having:
- Its own encryption key pair
- Metadata (unencrypted) including creation time and validity period
- Immutable value storage
- Atomic version switching via symlink updates
Vault System
Vaults provide logical separation of secrets, each with its own long-term key and unlocker set. This allows for complete isolation between different contexts (work, personal, projects).
Installation
Build from source:
git clone <repository>
cd secret
make build
Quick Start
-
Initialize the secret manager:
secret init
This creates the default vault and prompts for a BIP39 mnemonic phrase.
-
Generate a mnemonic (if needed):
secret generate mnemonic
-
Add a secret:
echo "my-password" | secret add myservice/password
-
Retrieve a secret:
secret get myservice/password
Commands Reference
Initialization
secret init
Initializes the secret manager with a default vault. Prompts for a BIP39 mnemonic phrase and creates the initial directory structure.
Environment Variables:
SB_SECRET_MNEMONIC
: Pre-set mnemonic phraseSB_UNLOCK_PASSPHRASE
: Pre-set unlock passphrase
Vault Management
secret vault list [--json]
/ secret vault ls
Lists all available vaults. The current vault is marked.
secret vault create <name>
Creates a new vault with the specified name.
secret vault select <name>
Switches to the specified vault for subsequent operations.
secret vault remove <name> [--force]
/ secret vault rm
⚠️ 🛑
DANGER: Permanently removes a vault and all its secrets. Like Unix rm
, this command does not ask for confirmation.
Requires --force if the vault contains secrets. With --force, will automatically switch to another vault if removing the current one.
--force, -f
: Force removal even if vault contains secrets- NO RECOVERY: All secrets in the vault will be permanently deleted
Secret Management
secret add <secret-name> [--force]
Adds a secret to the current vault. Reads the secret value from stdin.
--force, -f
: Overwrite existing secret
Secret Name Format: [a-z0-9\.\-\_\/]+
- Forward slashes (
/
) are converted to percent signs (%
) for storage - Examples:
database/password
,api.key
,ssh_private_key
secret get <secret-name> [--version <version>]
Retrieves and outputs a secret value to stdout.
--version, -v
: Get a specific version (default: current)
secret list [filter] [--json]
/ secret ls
Lists all secrets in the current vault. Optional filter for substring matching.
secret remove <secret-name>
/ secret rm
⚠️ 🛑
DANGER: Permanently removes a secret and ALL its versions. Like Unix rm
, this command does not ask for confirmation.
- NO RECOVERY: Once removed, the secret cannot be recovered
- ALL VERSIONS DELETED: Every version of the secret will be permanently deleted
Version Management
secret version list <secret-name>
/ secret version ls
Lists all versions of a secret showing creation time, status, and validity period.
secret version promote <secret-name> <version>
Promotes a specific version to current by updating the symlink. Does not modify any timestamps, allowing for rollback scenarios.
secret version remove <secret-name> <version>
/ secret version rm
⚠️ 🛑
DANGER: Permanently removes a specific version of a secret. Like Unix rm
, this command does not ask for confirmation.
- NO RECOVERY: Once removed, this version cannot be recovered
- Cannot remove the current version (must promote another version first)
Key Generation
secret generate mnemonic
Generates a cryptographically secure BIP39 mnemonic phrase.
secret generate secret <name> [--length=16] [--type=base58] [--force]
Generates and stores a random secret.
--length, -l
: Length of generated secret (default: 16)--type, -t
: Type of secret (base58
,alnum
)--force, -f
: Overwrite existing secret
Unlocker Management
secret unlocker list [--json]
/ secret unlocker ls
Lists all unlockers in the current vault with their metadata.
secret unlocker add <type> [options]
Creates a new unlocker of the specified type:
Types:
passphrase
: Traditional passphrase-protected unlockerpgp
: Uses an existing GPG key for encryption/decryptionkeychain
: macOS Keychain integration (macOS only)
Options:
--keyid <id>
: GPG key ID (required for PGP type)
secret unlocker remove <unlocker-id> [--force]
/ secret unlocker rm
⚠️ 🛑
DANGER: Permanently removes an unlocker. Like Unix rm
, this command does not ask for confirmation.
Cannot remove the last unlocker if the vault has secrets unless --force is used.
--force, -f
: Force removal of last unlocker even if vault has secrets- CRITICAL WARNING: Without unlockers and without your mnemonic phrase, vault data will be PERMANENTLY INACCESSIBLE
- NO RECOVERY: Removing all unlockers without having your mnemonic means losing access to all secrets forever
secret unlocker select <unlocker-id>
Selects an unlocker as the current default for operations.
Import Operations
secret import <secret-name> --source <filename>
Imports a secret from a file and stores it in the current vault under the given name.
secret vault import [vault-name]
Imports a mnemonic phrase into the specified vault (defaults to "default").
Encryption Operations
secret encrypt <secret-name> [--input=file] [--output=file]
Encrypts data using an Age key stored as a secret. If the secret doesn't exist, generates a new Age key.
secret decrypt <secret-name> [--input=file] [--output=file]
Decrypts data using an Age key stored as a secret.
Storage Architecture
Directory Structure
~/.local/share/secret/
├── vaults.d/
│ ├── default/
│ │ ├── unlockers.d/
│ │ │ ├── passphrase/ # Passphrase unlocker
│ │ │ └── pgp/ # PGP unlocker
│ │ ├── secrets.d/
│ │ │ ├── api%key/ # Secret: api/key
│ │ │ │ ├── versions/
│ │ │ │ │ ├── 20231215.001/ # Version directory
│ │ │ │ │ │ ├── pub.age # Version public key
│ │ │ │ │ │ ├── priv.age # Version private key (encrypted)
│ │ │ │ │ │ ├── value.age # Encrypted value
│ │ │ │ │ │ └── metadata.json # Unencrypted metadata
│ │ │ │ │ └── 20231216.001/ # Another version
│ │ │ │ └── current -> versions/20231216.001
│ │ │ └── database%password/ # Secret: database/password
│ │ │ ├── versions/
│ │ │ └── current -> versions/20231215.001
│ │ ├── vault-metadata.json # Vault metadata
│ │ ├── pub.age # Long-term public key
│ │ └── current-unlocker -> ../unlockers.d/passphrase
│ └── work/
│ ├── unlockers.d/
│ ├── secrets.d/
│ ├── vault-metadata.json
│ ├── pub.age
│ └── current-unlocker
└── currentvault -> vaults.d/default
Key Management and Encryption Flow
Long-term Keys
- Source: Derived from BIP39 mnemonic phrases using hierarchical deterministic (HD) key derivation
- Purpose: Master keys for each vault, used to encrypt secret-specific keys
- Storage: Public key stored as
pub.age
, private key encrypted by unlockers
Unlockers
Unlockers provide different authentication methods to access the long-term keys:
-
Passphrase Unlockers:
- Encrypted with user-provided passphrase
- Stored as encrypted Age keys
- Cross-platform compatible
-
PGP Unlockers:
- Uses existing GPG key infrastructure
- Leverages existing key management workflows
- Strong authentication through GPG
-
Keychain Unlockers (macOS only):
- Stores unlock keys in macOS Keychain
- Protected by system authentication (Touch ID, password)
- Automatic unlocking when Keychain is unlocked
- Cross-application integration
-
Secure Enclave Unlockers (macOS - planned):
- Hardware-backed key storage using Apple Secure Enclave
- Currently partially implemented but non-functional
- Requires Apple Developer Program membership and code signing entitlements
- Full implementation blocked by entitlement requirements
Each vault maintains its own set of unlockers and one long-term key. The long-term key is encrypted to each unlocker, allowing any authorized unlocker to access vault secrets.
Secret-specific Keys
- Each secret has its own encryption key pair
- Private key encrypted to the vault's long-term key
- Provides forward secrecy and granular access control
Environment Variables
SB_SECRET_STATE_DIR
: Custom state directory locationSB_SECRET_MNEMONIC
: Pre-set mnemonic phrase (avoids interactive prompt)SB_UNLOCK_PASSPHRASE
: Pre-set unlock passphrase (avoids interactive prompt)SB_GPG_KEY_ID
: GPG key ID for PGP unlockers
Security Features
Encryption
- Uses the Age encryption library with X25519 keys
- All private keys are encrypted at rest
- No plaintext secrets stored on disk
Access Control
- Multiple authentication methods supported
- Hierarchical key architecture provides defense in depth
- Vault isolation prevents cross-contamination
Forward Secrecy
- Per-version encryption keys limit exposure if compromised
- Each version is independently encrypted
- Long-term keys protected by multiple unlocker layers
- Historical versions remain encrypted with their original keys
Hardware Integration
- Hardware token support via PGP/GPG integration
- macOS Keychain integration for system-level security
- Secure Enclave support planned (requires Apple Developer Program)
Examples
Basic Workflow
# Initialize with a new mnemonic
secret generate mnemonic # Copy the output
secret init # Paste the mnemonic when prompted
# Add some secrets
echo "supersecret123" | secret add database/prod/password
echo "api-key-xyz" | secret add services/api/key
echo "ssh-private-key-content" | secret add ssh/servers/web01
# List and retrieve secrets
secret list
secret get database/prod/password
secret get services/api/key
# Remove a secret ⚠️ 🛑 (NO CONFIRMATION - PERMANENT!)
secret remove ssh/servers/web01
Multi-vault Setup
# Create separate vaults for different contexts
secret vault create work
secret vault create personal
# Work with work vault
secret vault select work
echo "work-db-pass" | secret add database/password
secret unlocker add passphrase # Add passphrase authentication
# Switch to personal vault
secret vault select personal
echo "personal-email-pass" | secret add email/password
# List all vaults
secret vault list
# Remove a vault ⚠️ 🛑 (NO CONFIRMATION - PERMANENT!)
secret vault remove personal --force
Advanced Authentication
# Add multiple unlock methods
secret unlocker add passphrase # Password-based
secret unlocker add pgp --keyid ABCD1234 # GPG key
secret unlocker add keychain # macOS Keychain (macOS only)
# List unlockers
secret unlocker list
# Select a specific unlocker
secret unlocker select <unlocker-id>
# Remove an unlocker ⚠️ 🛑 (NO CONFIRMATION!)
secret unlocker remove <unlocker-id>
Version Management
# List all versions of a secret
secret version list database/prod/password
# Promote an older version to current
secret version promote database/prod/password 20231215.001
# Remove an old version ⚠️ 🛑 (NO CONFIRMATION - PERMANENT!)
secret version remove database/prod/password 20231214.001
Encryption/Decryption with Age Keys
# Generate an Age key and store it as a secret
secret generate secret encryption/mykey
# Encrypt a file using the stored key
secret encrypt encryption/mykey --input document.txt --output document.txt.age
# Decrypt the file
secret decrypt encryption/mykey --input document.txt.age --output document.txt
Technical Details
Cryptographic Primitives
- Key Derivation: BIP32/BIP39 hierarchical deterministic key derivation
- Encryption: Age (X25519 + ChaCha20-Poly1305)
- Key Exchange: X25519 elliptic curve Diffie-Hellman
- Authentication: Poly1305 MAC
- Hashing: Double SHA-256 for public key identification
File Formats
- Age Files: Standard Age encryption format (.age extension)
- Metadata: Unencrypted JSON format with timestamps and type information
- Vault Metadata: JSON containing vault name, creation time, derivation index, and public key hash
Vault Management
- Derivation Index: Each vault uses a unique derivation index from the mnemonic
- Public Key Hash: Double SHA-256 hash of the index-0 public key identifies vaults from the same mnemonic
- Automatic Key Derivation: When creating vaults with a mnemonic, keys are automatically derived
Cross-Platform Support
- macOS: Full support including Keychain and planned Secure Enclave integration
- Linux: Full support (excluding macOS-specific features)
- Windows: Basic support (filesystem operations only)
Security Considerations
Threat Model
- Protects against unauthorized access to secret values
- Provides defense against compromise of individual components
- Supports hardware-backed authentication where available
Best Practices
- Use strong, unique passphrases for unlockers
- Enable hardware authentication (Keychain, hardware tokens) when available
- Regularly audit unlockers and remove unused ones
- Keep mnemonic phrases securely backed up offline
- Use separate vaults for different security contexts
Limitations
- Requires access to unlockers for secret retrieval
- Mnemonic phrases must be securely stored and backed up
- Hardware features limited to supported platforms
Development
Building
make build # Build binary
make test # Run tests
make lint # Run linter
Testing
The project includes comprehensive tests:
make test # Run all tests
go test ./... # Unit tests
go test -tags=integration -v ./internal/cli # Integration tests
Features
- Multiple Authentication Methods: Supports passphrase, PGP, and macOS Keychain unlockers
- Vault Isolation: Complete separation between different vaults
- Per-Secret Encryption: Each secret has its own encryption key
- BIP39 Mnemonic Support: Keyless operation using mnemonic phrases
- Cross-Platform: Works on macOS, Linux, and other Unix-like systems
Author
Made with love and lots of expensive SOTA AI by sneak in Berlin in the summer of 2025.
Released as a free software gift to the world, no strings attached, under the WTFPL license.
Contact: sneak@sneak.berlin
https://keys.openpgp.org/vks/v1/by-fingerprint/5539AD00DE4C42F3AFE11575052443F4DF2A55C2