Secure secrets management with age encryption.
Seclusor is a library-first Rust project that lets developers, DevSecOps engineers, and integrators encrypt secrets with age. It provides a full CLI, secure runtime injection via seclusor run, and first-class bindings for Rust, Go, and TypeScript.
Important: While armored secrets can be stored in git, this is not always advisable. See App Note 01: Git Storage of Armored Secrets for the risk continuum and guidance by sensitivity level.
Lifecycle Phase: alpha | Current version: v0.1.6 (inline runtime fix, workflow scenarios guide) | See VERSION and CHANGELOG.md
Secrets don't belong in plaintext, but they often need to live near the code that uses them. Common alternatives include:
- Cloud / managed secret stores (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault) — excellent but require network calls and infrastructure
- Mozilla SOPS — git-friendly but lacks a library API (requires shelling out)
- git-crypt — transparent but offers no per-field visibility or flexible formats
- Password managers (1Password, Bitwarden/Vaultwarden) — great for individuals, with varying automation support
- HSMs — appropriate for the highest-security keys and root material
- Roll your own — time-consuming and error-prone
Seclusor fills the gap for teams that want local-first, library-native, git-compatible secret management with strong defaults.
- Modern age encryption: X25519 for team sharing and scrypt for passphrases. Strong defaults with size limits.
- Two storage codecs: Bundle (opaque, safest) and inline (
sec:age:v1:) for when you need readable structure. Convert between them easily. - Asset signing: Generate age-protected Ed25519 signing keys, sign arbitrary files, and verify detached
seclusor.signature.v1envelopes by expected public key or fingerprint. - Ed25519 primitives (
seclusor-crypto/signingfeature): Generate keypairs, sign messages, and verify signatures. Available in Rust (v0.1.1) and Go (v0.1.2). Secret keys are zeroized on drop. - Library-first design: Rust consumers can use
seclusor-crypto,seclusor-sign,seclusor-codec, andseclusor-keyringdirectly. Go currently exposes the lower-level Ed25519 primitives; asset-signing envelope bindings are deferred to a later binding pass. - Blob encryption: Encrypt any file (shell scripts, configs, binary tokens) with
secrets blob encrypt/decrypt. No JSON required. - Passphrase-protected identities: Encrypt identity files at rest with a passphrase, like SSH keys. Four input channels for automation.
- Secure CLI: Full command set including
secrets run(injects secrets without exposing them in CLI args, history, or process lists). - Safe by default: Redaction, stdout purity, no secrets in arguments, strict validation.
- Audience-focused: Great for developers (local workflows), DevSecOps (secure pipelines), and integrators (library usage).
For guidance on storing armored files in git, see App Note 01. For runtime patterns see App Note 02.
[dependencies]
seclusor-crypto = "0.1" # encrypt/decrypt with age
seclusor-keyring = "0.1" # identity generation, recipient management
seclusor-core = "0.1" # domain types, validation
# Optional: add Ed25519 primitive sign/verify
seclusor-crypto = { version = "0.1", features = ["signing"] }
# Optional: add asset signature envelope support
seclusor-sign = "0.1"use seclusor_crypto::{encrypt, decrypt, load_identity_file};
// Encrypt a secret for one or more age recipients
let ciphertext = encrypt(b"example-secret-value-12345", &recipients)?;
// Decrypt using an identity file
let identities = load_identity_file("~/.config/seclusor/identity.txt")?;
let plaintext = decrypt(&ciphertext, &identities)?;Ed25519 primitive signing (requires features = ["signing"]):
use seclusor_crypto::{generate_signing_keypair, sign, verify};
let keypair = generate_signing_keypair()?;
let sig = sign(keypair.secret_key(), b"payload")?;
verify(keypair.public_key(), b"payload", &sig)?;
// Keys stored encrypted at rest — serialize the seed and encrypt with age
let seed_bytes = seclusor_crypto::signing_secret_key_to_bytes(keypair.secret_key());
let encrypted_key = encrypt(&seed_bytes, &recipients)?;Asset signing from the CLI:
seclusor keys signing generate \
--output ~/.config/seclusor/release-signing.key.age \
--recipient age1...
seclusor assets sign \
--input dist/seclusor.tar.gz \
--signing-key ~/.config/seclusor/release-signing.key.age \
--identity-file ~/.config/seclusor/identity.txt \
--signer-label release-signing \
--claimed-at 2026-05-17T12:00:00Z
seclusor assets verify \
--input dist/seclusor.tar.gz \
--public-key <base64url-public-key>Create a secrets file and run a command with injected environment variables (no secrets in shell history or process list):
# 1. Create identity (once)
seclusor keys age identity generate --output ~/.config/seclusor/identity.txt
# 2. Create and armor a simple secrets file
seclusor secrets init --output secrets.json --project myapp
# Add credentials with `secrets set`; the JSON shape is an object like:
# {"DB_PASSWORD":{"type":"secret","value":"..."}}
seclusor secrets set \
--file secrets.json \
--project myapp \
--key DB_PASSWORD \
--value "example-db-password-9xK7mP2qR8vT" \
--description "primary application database password"
seclusor secrets bundle encrypt --file secrets.json --output secrets.age --recipient age1...yourrecipient...
# 3. Run with injected secrets (contrived example)
seclusor secrets run \
--file secrets.age \
--identity-file ~/.config/seclusor/identity.txt \
--project myapp \
--allow DB_* \
-- env | grep DB_Other access methods are supported: exporting to .env files, library calls, or building a simple secret server.
See the App Notes for detailed guidance.
| Code | Name | When |
|---|---|---|
| 0 | Success | Command completed successfully |
| 1 | Failure | Generic failure |
| 30 | ConfigInvalid | Configuration or document validation fail |
| 50 | FileNotFound | Required file not found |
Seclusor is a Rust workspace with seven crates. Library crates are the architecture — CLI and FFI are thin consumers.
seclusor/
├── crates/
│ ├── seclusor-core/ # Domain types, validation, env export/import
│ ├── seclusor-crypto/ # age encryption (X25519 + scrypt), Ed25519 signing (feature-gated)
│ ├── seclusor-sign/ # Detached asset signature envelopes
│ ├── seclusor-codec/ # Bundle + inline codecs, format conversion
│ ├── seclusor-keyring/ # Key generation, recipient discovery, rekey
│ ├── seclusor-ffi/ # C-ABI exports (cdylib + staticlib)
│ └── seclusor/ # CLI binary (thin adapter)
├── bindings/
│ ├── go/seclusor/ # Go CGo wrapper
│ └── typescript/ # TypeScript NAPI-RS addon
├── schemas/
│ └── seclusor/v1.0.0/ # JSON Schema for secrets documents
└── docs/
└── decisions/ # ADRs, SDRs, DDRs
seclusor-core ← leaf, no internal deps
↑
seclusor-crypto ← depends on core
↑
seclusor-sign ← depends on crypto
↑
seclusor-codec ← depends on core, crypto
seclusor-keyring ← depends on core, crypto
↑
seclusor-ffi ← depends on all library crates
seclusor (CLI) ← depends on all library crates
Bundle — Whole-file age encryption. The entire secrets document is a single opaque .age ciphertext. Best for: distribution, archival, environments where you want zero plaintext structure visible.
Inline — Per-field encryption with sec:age:v1:<base64> markers. The document structure (keys, project slugs) remains readable; only values are encrypted. Best for: git diffs, code review, config files where you need to see what's there without decrypting.
Convert between them freely:
seclusor secrets convert --input secrets.age --output secrets-inline.json --from bundle --to inline --identity-file ./identity.txt --recipient age1...
seclusor secrets convert --input secrets-inline.json --output secrets.age --from inline --to bundle --identity-file ./identity.txt --recipient age1...The Go bindings (bindings/go/seclusor) use CGo over the seclusor-ffi static library and provide full access to secret document management, encryption, and keyring operations. Prebuilt static libraries for all supported platforms are committed to the repo and resolved at build time.
Current Go surface: encryption, secret document operations (List, Get, ExportEnv), bundle encrypt/decrypt, keyring management, and Ed25519 signing in v0.1.2.
import "github.com/3leaps/seclusor/bindings/go/seclusor"
handle, err := seclusor.LoadSecretsJSON(string(jsonBytes))
if err != nil {
log.Fatal(err)
}
defer handle.Close()
keys, err := handle.List("")
env, err := handle.ExportEnv("", "", false)TypeScript bindings via NAPI-RS are in development.
The seclusor-ffi crate exposes a C-ABI surface using:
- Opaque handles for stateful objects (
SeclusorSecretsHandle,SeclusorKeyringHandle) - JSON-over-FFI for complex return types (credential views, env exports, key lists)
- Thread-local error state via
seclusor_last_error()with result code enum - Panic-safe boundary — all exports wrap logic in
catch_unwind
The FFI surface established in v0.1.0 is treated as stable for the v0.1.x line. Ed25519 signing was intentionally excluded from the first v0.1.1 FFI pass, then added in v0.1.2 after the Rust-side signing contract was reviewed. See ADR-0008 and ADR-0011 for the full contract and rationale.
Seclusor uses age (ADR-0002):
- X25519 key exchange for multi-recipient encryption
- ChaCha20-Poly1305 authenticated encryption
- scrypt passphrase-based encryption for ad-hoc sharing
- 16 MiB decrypt size limit, 1 MiB inline value limit
seclusor-sign adds detached asset signatures using the
seclusor.signature.v1 envelope from DDR-0004:
- Asset-bound verification — stream-hashes the candidate asset before signature verification
- Strict envelope parser — duplicate keys, unknown fields,
null, padded base64url, and malformed metadata fail closed - Expected-key trust model — verification requires an expected public key or fingerprint by default
- Age-protected signing-key files — encrypted Ed25519 seeds with
0600permissions, owner checks on Unix, and repository-root pathguard
The seclusor-crypto/signing feature provides the lower-level Ed25519 primitives (added in v0.1.1):
- Ed25519 plain mode — 32-byte seed secret key, 32-byte public key, 64-byte signature, opaque message bytes
SigningSecretKeyimplementsZeroize/ZeroizeOnDrop; noDebug,Clone, or serde to prevent accidental key exposure- Four content-free error variants — no key material, message bytes, or signature bytes ever in error strings
- Rust-native in v0.1.1; Go signing bindings added in v0.1.2. TypeScript signing remains out of scope.
See ADR-0011, DDR-0002, and DDR-0004 for the full signing contracts.
- Secrets never appear in CLI arguments or shell history
getredacts by default;--revealrequired to see valuesget --show-descriptionprints description metadata onlylist --verboseprintsKEY<TAB>descriptionwithout exposing valueslistnever shows values- Identity files require
0600permissions on Unix - Key material is never written to the repository root (pathguard)
- Data output goes to stdout, diagnostics to stderr (stdout purity)
See SDR-0002 for the secret input channel policy.
| Platform | Target | Status |
|---|---|---|
| Linux x64 (glibc) | x86_64-unknown-linux-gnu |
Primary |
| Linux arm64 (glibc) | aarch64-unknown-linux-gnu |
Primary |
| Linux x64 (musl) | x86_64-unknown-linux-musl |
Supported |
| Linux arm64 (musl) | aarch64-unknown-linux-musl |
Supported |
| macOS arm64 | aarch64-apple-darwin |
Supported |
| Windows x64 | x86_64-pc-windows-msvc |
Future |
# Build
cargo build
# Test
cargo test
# Final local PR gate
make pr-finalcargo fmt --check— zero diffcargo clippy --workspace -- -Dwarnings— zero warningscargo test— all tests passcargo deny check— all dependencies permissively licensedmake pr-final— CI-equivalent Rust checks plus Go/TypeScript binding tests
- License-clean: All dependencies use MIT, Apache-2.0, or compatible licenses
- Auditable:
cargo treefor the full dependency graph - SBOM-ready: Compatible with CycloneDX via
cargo sbom - No runtime network calls: All functionality is local
cargo deny check licenses
cargo auditSeclusor is part of the 3leaps platform library family:
| Library | Scope | Purpose |
|---|---|---|
| seclusor | Secrets management | Age encryption, secure runner, and cross-language library |
| ipcprims | Inter-process communication | Framed, multiplexed IPC primitives |
| sysprims | System operations | Process control and system interaction primitives |
Seclusor is a key dependency for the Lanyte secure agent platform, which uses seclusor-crypto and seclusor-keyring directly for session attestation and glassbreak credential handling.
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Subject to 3 Leaps OSS policies.
See MAINTAINERS.md for governance and AGENTS.md for AI contributor protocols.
Built by the 3 Leaps team
Part of the Fulmen Ecosystem