Make the README's storage and file format text match the code (closes #102)
check / check (push) Failing after 3s
check / check (push) Failing after 3s
The directory tree shows `current` and `currentvault` as plain files holding a name, a version's metadata as the encrypted `metadata.age`, the real state directory under the user's configuration directory, and the `lock` file. `version promote` rewrites `current`. File Formats tells unencrypted vault and unlocker metadata from encrypted version metadata; `pub.age` is plain text and vault metadata holds no vault name. Unlocker bullets lose Touch ID claims the code does not set up, and the Secure Enclave only decrypts. Per-version keys no longer claim forward secrecy. Testing lists only `make test`. Model: opus-5-5
This commit is contained in:
@@ -180,8 +180,8 @@ period.
|
|||||||
|
|
||||||
#### `secret version promote <secret-name> <version>`
|
#### `secret version promote <secret-name> <version>`
|
||||||
|
|
||||||
Promotes a specific version to current by updating the symlink. Does not modify
|
Promotes a specific version to current by rewriting the secret's `current` file
|
||||||
any timestamps, allowing for rollback scenarios.
|
to name it. Does not modify any timestamps, allowing for rollback scenarios.
|
||||||
|
|
||||||
#### `secret version remove <secret-name> <version> [--force]` / `secret version rm` ⚠️ 🛑
|
#### `secret version remove <secret-name> <version> [--force]` / `secret version rm` ⚠️ 🛑
|
||||||
|
|
||||||
@@ -278,8 +278,13 @@ Decrypts data using an Age key stored as a secret.
|
|||||||
|
|
||||||
### Directory Structure
|
### Directory Structure
|
||||||
|
|
||||||
|
The state directory is `berlin.sneak.pkg.secret` in the user's configuration
|
||||||
|
directory: on Linux `$XDG_CONFIG_HOME`, or `~/.config` when that is unset; on
|
||||||
|
macOS `~/Library/Application Support`. When `SB_SECRET_STATE_DIR` is set, it is
|
||||||
|
the state directory instead. On Linux:
|
||||||
|
|
||||||
```
|
```
|
||||||
~/.local/share/secret/
|
~/.config/berlin.sneak.pkg.secret/
|
||||||
├── vaults.d/
|
├── vaults.d/
|
||||||
│ ├── default/
|
│ ├── default/
|
||||||
│ │ ├── unlockers.d/
|
│ │ ├── unlockers.d/
|
||||||
@@ -292,12 +297,12 @@ Decrypts data using an Age key stored as a secret.
|
|||||||
│ │ │ │ │ │ ├── pub.age # Version public key
|
│ │ │ │ │ │ ├── pub.age # Version public key
|
||||||
│ │ │ │ │ │ ├── priv.age # Version private key (encrypted)
|
│ │ │ │ │ │ ├── priv.age # Version private key (encrypted)
|
||||||
│ │ │ │ │ │ ├── value.age # Encrypted value
|
│ │ │ │ │ │ ├── value.age # Encrypted value
|
||||||
│ │ │ │ │ │ └── metadata.json # Unencrypted metadata
|
│ │ │ │ │ │ └── metadata.age # Encrypted metadata
|
||||||
│ │ │ │ │ └── 20231216.001/ # Another version
|
│ │ │ │ │ └── 20231216.001/ # Another version
|
||||||
│ │ │ │ └── current -> versions/20231216.001
|
│ │ │ │ └── current # Current version's name: 20231216.001
|
||||||
│ │ │ └── database%password/ # Secret: database/password
|
│ │ │ └── database%password/ # Secret: database/password
|
||||||
│ │ │ ├── versions/
|
│ │ │ ├── versions/
|
||||||
│ │ │ └── current -> versions/20231215.001
|
│ │ │ └── current # Current version's name: 20231215.001
|
||||||
│ │ ├── vault-metadata.json # Vault metadata
|
│ │ ├── vault-metadata.json # Vault metadata
|
||||||
│ │ ├── pub.age # Long-term public key
|
│ │ ├── pub.age # Long-term public key
|
||||||
│ │ └── current-unlocker # Current unlocker's directory name
|
│ │ └── current-unlocker # Current unlocker's directory name
|
||||||
@@ -307,9 +312,13 @@ Decrypts data using an Age key stored as a secret.
|
|||||||
│ ├── vault-metadata.json
|
│ ├── vault-metadata.json
|
||||||
│ ├── pub.age
|
│ ├── pub.age
|
||||||
│ └── current-unlocker
|
│ └── current-unlocker
|
||||||
└── currentvault -> vaults.d/default
|
├── currentvault # Current vault's name: default
|
||||||
|
└── lock # Locked by each command that changes anything
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`current`, `currentvault` and `current-unlocker` are plain files that each hold
|
||||||
|
one name. Changing one replaces it in one rename, so it is never half-written.
|
||||||
|
|
||||||
### Key Management and Encryption Flow
|
### Key Management and Encryption Flow
|
||||||
|
|
||||||
#### 1: Long-term Keys
|
#### 1: Long-term Keys
|
||||||
@@ -336,7 +345,7 @@ Unlockers provide different authentication methods to access the long-term keys:
|
|||||||
|
|
||||||
3. **Keychain Unlockers** (macOS only):
|
3. **Keychain Unlockers** (macOS only):
|
||||||
- Stores unlock keys in macOS Keychain
|
- Stores unlock keys in macOS Keychain
|
||||||
- Protected by system authentication (Touch ID, password)
|
- Kept on this Mac only: the keychain item is never synced to other devices
|
||||||
- Automatic unlocking when Keychain is unlocked
|
- Automatic unlocking when Keychain is unlocked
|
||||||
- Cross-application integration
|
- Cross-application integration
|
||||||
|
|
||||||
@@ -344,8 +353,10 @@ Unlockers provide different authentication methods to access the long-term keys:
|
|||||||
- Hardware-backed key storage using Apple Secure Enclave
|
- Hardware-backed key storage using Apple Secure Enclave
|
||||||
- Uses `sc_auth` / CryptoTokenKit for SE key management (no Apple Developer
|
- Uses `sc_auth` / CryptoTokenKit for SE key management (no Apple Developer
|
||||||
Program required)
|
Program required)
|
||||||
- ECIES encryption: vault long-term key encrypted directly by SE hardware
|
- ECIES encryption: the vault long-term key is encrypted directly to the SE
|
||||||
- Protected by biometric authentication (Touch ID) or system password
|
key, and only the SE can decrypt it
|
||||||
|
- The SE key cannot leave this Mac; using it asks for no Touch ID or
|
||||||
|
password
|
||||||
|
|
||||||
Each vault maintains its own set of unlockers and one long-term key. The
|
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
|
long-term key is encrypted to each unlocker, allowing any authorized unlocker to
|
||||||
@@ -355,7 +366,7 @@ access vault secrets.
|
|||||||
|
|
||||||
- Each secret version has its own encryption key pair
|
- Each secret version has its own encryption key pair
|
||||||
- Private key encrypted to the vault's long-term key
|
- Private key encrypted to the vault's long-term key
|
||||||
- Provides forward secrecy and granular access control
|
- A version's private key decrypts only that version's value and metadata
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
@@ -505,17 +516,21 @@ secret decrypt encryption/mykey --input document.txt.age --output document.txt
|
|||||||
|
|
||||||
### File Formats
|
### File Formats
|
||||||
|
|
||||||
- **age Files**: Standard age encryption format (.age extension)
|
- **age Files**: Standard age encryption format (.age extension), except
|
||||||
- **Metadata**: Unencrypted JSON format with timestamps and type information
|
`pub.age`, which holds an age public key as text
|
||||||
- **Vault Metadata**: JSON containing vault name, creation time, derivation
|
- **Metadata**: `vault-metadata.json` and `unlocker-metadata.json` are
|
||||||
index, and public key hash
|
unencrypted JSON with a creation time, and `unlocker-metadata.json` also
|
||||||
|
records the unlocker's type; a version's `metadata.age` is JSON encrypted to
|
||||||
|
the version's public key
|
||||||
|
- **Vault Metadata**: JSON containing creation time, derivation index, and the
|
||||||
|
public key hashes described below
|
||||||
|
|
||||||
### Vault Management
|
### Vault Management
|
||||||
|
|
||||||
- **Derivation Index**: Each vault uses a unique derivation index from the
|
- **Derivation Index**: Each vault uses a unique derivation index from the
|
||||||
mnemonic, and thus a unique key pair
|
mnemonic, and thus a unique key pair
|
||||||
- **Public Key Hash**: Double SHA-256 hash of the index-0 public key identifies
|
- **Public Key Hash**: Double SHA-256 hash of the vault's public key; the same
|
||||||
vaults from the same mnemonic
|
hash of the index-0 public key identifies vaults from the same mnemonic
|
||||||
- **Automatic Key Derivation**: When creating vaults with a mnemonic, keys are
|
- **Automatic Key Derivation**: When creating vaults with a mnemonic, keys are
|
||||||
automatically derived
|
automatically derived
|
||||||
|
|
||||||
@@ -566,8 +581,6 @@ The project includes comprehensive tests:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
make test # Run all tests
|
make test # Run all tests
|
||||||
go test ./... # Unit tests
|
|
||||||
go test -tags=integration -v ./internal/cli # Integration tests
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Entrypoints
|
## Entrypoints
|
||||||
|
|||||||
@@ -18,6 +18,17 @@ https://git.eeqj.de/sneak/secret/milestone/12
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-04: README's Storage Architecture, `secret version promote`,
|
||||||
|
Technical Details and Testing text matches the code
|
||||||
|
(https://git.eeqj.de/sneak/secret/issues/102). `current` and
|
||||||
|
`currentvault` are plain files holding a name, not symbolic links; a
|
||||||
|
version's metadata is the encrypted `metadata.age`; the state directory is
|
||||||
|
`berlin.sneak.pkg.secret` in the user's configuration directory, not
|
||||||
|
`~/.local/share/secret`, and holds the `lock` file. Also corrected: the
|
||||||
|
code sets up no Touch ID for the keychain or Secure Enclave unlocker, and
|
||||||
|
the Secure Enclave only decrypts; per-version keys give no forward
|
||||||
|
secrecy; `pub.age` is not age-encrypted; vault metadata holds no vault
|
||||||
|
name. Testing lists only `make test`.
|
||||||
- 2026-10-04: A failed `secret unlocker add keychain` or
|
- 2026-10-04: A failed `secret unlocker add keychain` or
|
||||||
`secret unlocker add secure-enclave` no longer leaves its keychain item or
|
`secret unlocker add secure-enclave` no longer leaves its keychain item or
|
||||||
Secure Enclave key behind (https://git.eeqj.de/sneak/secret/issues/89).
|
Secure Enclave key behind (https://git.eeqj.de/sneak/secret/issues/89).
|
||||||
|
|||||||
Reference in New Issue
Block a user