Vault Format
Overview
Section titled “Overview”dotsecenv vaults use a versioned JSONL format. The format includes a version field in the header JSON, allowing the format to evolve over time while maintaining backward compatibility.
Current Format (v1) Current
Section titled “Current Format (v1) ”The vault file consists of:
- A header marker line that identifies the file as a dotsecenv vault
- A header JSON line that indexes identities and secrets for efficient lookup
- A data marker line that separates header from data
- Data entries: one JSON object per line (identities, secrets, values)
Structure
Section titled “Structure”# === VAULT HEADER v1 ==={"version":1,"identities":[["fingerprint1",4],["fingerprint2",5]],"secrets":{...}}# === VAULT DATA ==={"type":"identity","data":{...}}{"type":"secret","data":{...}}{"type":"value","secret":"KEY","data":{...}}Header Format
Section titled “Header Format”Identities are stored as an ordered array of [fingerprint, line] pairs, sorted by line number (order added):
{ "version": 1, "identities": [ ["ABC123DEF456", 4], ["XYZ789GHI012", 5] ], "secrets": { "DATABASE_URL": { "secret": 6, "values": [7, 8] } }}| Field | Type | Purpose |
|---|---|---|
version | int | Format version (currently 1) |
identities | array | Array of [fingerprint, line] pairs, sorted by line number |
secrets | object | Map of secret name to definition line and value lines |
Why Arrays for Identities?
Section titled “Why Arrays for Identities?”The array format preserves insertion order. That gives you deterministic output (the same vault always serializes identically), cleaner git diffs from consistent ordering, and an implicit audit trail since order reflects when identities were added.
Append-only semantics
Section titled “Append-only semantics”secret store always appends a new line to the vault. The file only grows forward; no dotsecenv command mutates or removes a past entry.
On read, secret get returns the latest entry. vault describe shows every entry the vault holds, including the recipient list each was encrypted to and the signature on each line. Together with git log on the vault file, that history is the audit trail.
Three properties follow from this:
- Audit trail. What value was stored, when, and to whom it was encrypted is provable, going back to the first
secret storein the repo’s history. - Rotation safety. Adding a new value never overwrites or destroys the prior one. To replace a value, the new entry is written; the old entry remains as evidence.
- No edits in place. No CLI subcommand mutates a past entry. Every change is a new line.
What this means for revocation
Section titled “What this means for revocation”secret revoke and secret share only affect future writes. They do not rewrite past entries. A revoked recipient still decrypts every entry that was written while their fingerprint was on the recipient list, including entries already in the repo’s git history. The durable mitigation is to rotate the underlying secret at its source (issue a new database password, reissue the API key) and store the new value. See Rotate a Compromised GPG Key and Offboard a Departing Team Member for the end-to-end runbooks.
Entry Types
Section titled “Entry Types”Identity Entry
Section titled “Identity Entry”{ "type": "identity", "data": { "added_at": "2025-01-01T00:00:00Z", "algorithm": "RSA", "algorithm_bits": 4096, "fingerprint": "ABC123DEF456789012345678901234567890ABCD", "hash": "sha256:...", "public_key": "-----BEGIN PGP PUBLIC KEY BLOCK-----...", "signed_by": "ABC123DEF456789012345678901234567890ABCD", "signature": "...", "uid": "alice@example.com" }}Secret Definition
Section titled “Secret Definition”{ "type": "secret", "data": { "added_at": "2025-01-01T00:00:00Z", "hash": "sha256:...", "key": "DATABASE_URL", "signature": "...", "signed_by": "ABC123DEF456789012345678901234567890ABCD" }}Secret Value
Section titled “Secret Value”{ "type": "value", "secret": "DATABASE_URL", "data": { "added_at": "2025-01-01T00:00:00Z", "available_to": ["ABC123DEF456...", "XYZ789GHI012..."], "hash": "sha256:...", "signature": "...", "signed_by": "ABC123DEF456789012345678901234567890ABCD", "value": "base64-encoded-encrypted-blob" }}Version Detection
Section titled “Version Detection”dotsecenv detects the format version from the version field in the header JSON:
{"version":1,"identities":[...],"secrets":{...}}This allows efficient version detection by parsing just the header line.
Line Numbers
Section titled “Line Numbers”Line numbers in the header are 1-indexed (first line = 1):
| Line | Content |
|---|---|
| 1 | Header marker (# === VAULT HEADER v1 ===) |
| 2 | Header JSON |
| 3 | Data marker (# === VAULT DATA ===) |
| 4+ | Data entries (identities, secrets, values) |
The header indexes point to line numbers where each entry can be found, enabling O(1) lookups.
Future Versioning
Section titled “Future Versioning”The format version lets dotsecenv evolve while maintaining compatibility. Older vaults remain readable, new features can land without breaking existing vaults, and future versions may include automatic upgrades.
Validation
Section titled “Validation”dotsecenv validates vault structure on read:
- Header marker must match expected format
- Header JSON must parse successfully
- Data marker must be present
- Line numbers in header must point to valid entries
Use the validate command to check vault integrity:
dotsecenv validate