Architecture
Vault Structure
Section titled “Vault Structure”The vault is a JSONL (JSON Lines) file where each line is a self-contained JSON entry. This append-only format enables:
- Safe concurrent git merges
- Efficient incremental updates
- Complete audit trail
- Simple conflict resolution
File Format
Section titled “File Format”# === VAULT HEADER v1 ===Line 2: Header JSON with metadata and indexes# === VAULT DATA ===Line 4+: Identity entries, secret definitions, and secret valuesExample structure:
{"version":1,"identities":[["ABC123",4],["DEF456",5]],"secrets":{"DATABASE_URL":{"secret":6,"values":[7]}}}{"type":"identity","data":{"fingerprint":"ABC123","uid":"alice@example.com","algorithm":"RSA","algorithm_bits":4096}}{"type":"identity","data":{"fingerprint":"DEF456","uid":"bob@example.com","algorithm":"EdDSA","curve":"Ed25519"}}{"type":"secret","data":{"key":"DATABASE_URL","signed_by":"ABC123","added_at":"2025-01-01T00:00:00Z"}}{"type":"value","secret":"DATABASE_URL","data":{"available_to":["ABC123","DEF456"],"value":"encrypted..."}}Header (Line 2)
Section titled “Header (Line 2)”The header provides fast lookup without scanning the entire file:
| Field | Purpose |
|---|---|
version | Format version (currently 1) |
identities | Array of [fingerprint, line] pairs (sorted by line number) |
secrets | Map of secret name to definition line and value lines |
Entry Types
Section titled “Entry Types”Identity Entry:
{ "type": "identity", "data": { "fingerprint": "E60A1740BAEF49284D22EA7D3C376348F0921C59", "uid": "alice@example.com", "algorithm": "rsa4096", "created_at": "2025-01-01T00:00:00Z", "signature": "..." }}Secret Definition:
{ "type": "secret", "data": { "name": "DATABASE_PASSWORD", "created_by": "E60A1740BAEF49284D22EA7D3C376348F0921C59", "created_at": "2025-01-01T00:00:00Z", "namespace": null, "signature": "..." }}Secret Value:
{ "type": "value", "secret": "DATABASE_PASSWORD", "data": { "available_to": ["E60A1740...", "DEF456..."], "value": "base64-encoded-encrypted-blob", "created_by": "E60A1740...", "created_at": "2025-01-01T00:00:00Z", "signature": "..." }}Identity Model
Section titled “Identity Model”An identity represents a user who can access secrets. Each identity is:
- A GPG key (public + private)
- Identified by fingerprint (40-character hex)
- Stored in the vault (public key info only)
Identity Fields
Section titled “Identity Fields”| Field | Description |
|---|---|
fingerprint | 40-character GPG key fingerprint |
uid | User ID (typically email) from GPG key |
algorithm | Key algorithm and size (e.g., rsa4096, ed25519) |
created_at | When identity was added to vault |
signature | Signature proving identity owns the key |
Identity Workflow
Section titled “Identity Workflow”1. User generates GPG key pair2. Public key exported and shared3. Vault admin imports public key to keyring4. Vault admin shares a secret: dotsecenv secret share SECRET_NAME FINGERPRINT5. Identity is auto-added to vault and can now receive shared secretsSecret Model
Section titled “Secret Model”A secret has two components:
- Secret Definition — Name, creator, timestamps
- Secret Values — Encrypted versions of the actual value
Why Separate?
Section titled “Why Separate?”This separation enables:
- Multiple values per secret (version history)
- Different access per value (sharing/revoking)
- Audit trail of who changed what when
Namespaces
Section titled “Namespaces”Secrets can be namespaced using :: separator:
prod::DATABASE_URL → Production database URLstaging::DATABASE_URL → Staging database URLapi::JWT_SECRET → API service JWT secretBenefits:
- Environment isolation
- Organizational clarity
- Bulk operations (
prod::*)
Encrypted Value Structure
Section titled “Encrypted Value Structure”dotsecenv uses RFC 9580 (OpenPGP) with mandatory AEAD encryption. Each secret value is a blob containing:
┌─────────────────────────────────────────┐│ Encrypted Value Blob │├─────────────────────────────────────────┤│ AES-256-GCM nonce (96 bits) ││ AES-256-GCM ciphertext ││ AES-256-GCM auth tag (128 bits) ││ GPG-encrypted session key (per-user) ││ Detached GPG signature (FIPS 186-5) │└─────────────────────────────────────────┘The symmetric encryption follows NIST SP 800-38D (GCM mode), while signatures comply with FIPS 186-5 (Digital Signature Standard).
Decryption Flow
Section titled “Decryption Flow”1. Find entry where your fingerprint is in available_to2. Decrypt session key using your GPG private key3. Decrypt AES-256-GCM ciphertext using session key4. Verify signature against originator's public key5. Return plaintext valueAccess Control
Section titled “Access Control”The available_to field tracks who can decrypt each value:
{ "available_to": [ "E60A1740BAEF49284D22EA7D3C376348F0921C59", "ABC123DEF456789012345678901234567890ABCD" ]}Share Operation
Section titled “Share Operation”When you share a secret:
1. Decrypt value using your key2. Generate new random session key3. Re-encrypt value with new session key4. Encrypt session key for all recipients (including new one)5. Sign with your key6. Append new entry to vaultRevoke Operation
Section titled “Revoke Operation”When you revoke access:
1. Decrypt value using your key2. Generate new random session key3. Re-encrypt value with new session key4. Encrypt session key for remaining recipients (excluding revoked)5. Sign with your key6. Append new entry to vaultAppend-Only Design
Section titled “Append-Only Design”The vault never modifies existing entries—only appends:
| Operation | Action |
|---|---|
| Add secret | Append definition + value entry |
| Update secret | Append new value entry |
| Share secret | Append new value entry with more recipients |
| Revoke access | Append new value entry with fewer recipients |
| Add identity | Append identity entry |
Benefits
Section titled “Benefits”- Audit trail — Full history preserved
- Git-friendly — No conflicts from concurrent edits
- Crash-safe — Partial writes don’t corrupt existing data
- Simple merge — Just concatenate and dedupe
Defragmentation
Section titled “Defragmentation”Over time, the vault accumulates obsolete entries. The vault doctor command checks vault health and can defragment it:
dotsecenv vault doctorAfter displaying health checks, doctor offers to defragment if needed. This removes:
- Old value entries (keeps latest per recipient set)
- Obsolete entries
- Duplicate entries
Configuration
Section titled “Configuration”Configuration lives at ~/.config/dotsecenv/config (or $XDG_CONFIG_HOME/dotsecenv/config):
# Vault file path(s)vault: - ~/.config/dotsecenv/vault - ~/work/project/secrets/vault
# Active user fingerprintfingerprint: E60A1740BAEF49284D22EA7D3C376348F0921C59
# Approved algorithms (minimum strength)approved_algorithms: - rsa:3072 - ecdsa:p384 - eddsa:ed25519
# Strict mode: treat warnings as errorsstrict: falseConfig Override
Section titled “Config Override”# CLI flagdotsecenv -c /path/to/config secret get KEY
# Environment variableDOTSECENV_CONFIG=/path/to/config dotsecenv secret get KEYMulti-Vault Support
Section titled “Multi-Vault Support”dotsecenv can work with multiple vaults:
vault: - name: personal path: ~/.config/dotsecenv/vault - name: work path: ~/work/secrets/vaultAccess by name or index:
dotsecenv secret get -v personal DATABASE_PASSWORDdotsecenv secret get -v 2 API_KEY # Index is 1-basedFile Permissions
Section titled “File Permissions”Recommended permissions:
| File | Mode | Notes |
|---|---|---|
| Config | 600 | User read/write only |
| Vault | 600 | User read/write only |
| GPG home | 700 | Directory: user only |
| Private key | 600 | User read/write only |
dotsecenv warns if permissions are too open.
Integration Points
Section titled “Integration Points”Vault files are designed for git:
# Add vault to gitgit add vaultgit commit -m "Add secrets"
# On conflict (rare due to append-only)git merge --strategy-option=ours # or theirs, then re-shareShell Plugins
Section titled “Shell Plugins”The .secenv file syntax:
# Load secret with same name as variableDATABASE_PASSWORD={dotsecenv}
# Load specific secretAPI_KEY={dotsecenv/prod::API_KEY}
# Plain values (not encrypted)DATABASE_HOST=localhost# GitHub Actions example- name: Setup GPG run: echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --import
# Install dotsecenv in your GitHub workflow- uses: dotsecenv/dotsecenv@v0
- name: Get secrets run: | export API_KEY=$(dotsecenv secret get API_KEY) ./deploy.shData Flow Summary
Section titled “Data Flow Summary”┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ User Input │ ──▶ │ dotsecenv │ ──▶ │ Vault ││ (stdin) │ │ CLI │ │ (JSONL) │└─────────────┘ └─────────────┘ └─────────────┘ │ ▼ ┌─────────────┐ │ GPG │ │ (Encrypt/ │ │ Decrypt) │ └─────────────┘All cryptographic operations go through GPG. dotsecenv orchestrates the workflow and manages the vault format.