This document explains Secretariat's security design and implementation.
Secretariat uses a defense-in-depth approach with multiple security layers:
- Encryption at Rest - AES-256-GCM for all stored secrets
- Key Protection - Master key stored in OS keychain
- Access Control - Per-app, per-secret permissions
- Audit Logging - Complete access history
- Memory Safety - Automatic secret zeroing
Secretariat uses AES-256-GCM (Galois/Counter Mode), which provides:
- 256-bit key strength - Resistant to brute force attacks
- Authenticated encryption - Detects tampering automatically
- 128-bit authentication tag - Cryptographic integrity verification
┌─────────────────────────────────────────────────────┐
│ Secret Storage │
├─────────────────────────────────────────────────────┤
│ plaintext ─────┐ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ AES-256-GCM │ │
│ │ ┌─────────┐ ┌──────────┐ ┌───────────┐ │ │
│ │ │ Master │ │ Random │ │ Plaintext │ │ │
│ │ │ Key │ │ Nonce │ │ Data │ │ │
│ │ │(32 bytes)│ │(12 bytes)│ │(variable) │ │ │
│ │ └────┬────┘ └────┬─────┘ └─────┬─────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ Encryption │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ Nonce │ Ciphertext │ Auth Tag (16B) │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ SQLite BLOB │ │
│ │ (encrypted) │ │
│ └────────────────┘ │
└─────────────────────────────────────────────────────┘
-
Unique Nonce per Operation
- Every encryption generates a fresh 96-bit random nonce
- Prevents nonce reuse attacks
- Provides semantic security (same plaintext → different ciphertext)
-
Authentication Tag
- 128-bit GCM authentication tag appended to ciphertext
- Verified before decryption
- Detects any modification to ciphertext
-
Fail-Secure Decryption
- Wrong key → authentication failure
- Modified ciphertext → authentication failure
- Truncated data → authentication failure
The master key is derived from the user's password using Argon2id:
Password + Salt ─────► Argon2id ─────► 256-bit Master Key
│
├── Memory: 64 MB
├── Iterations: 3
└── Parallelism: 4
Argon2id parameters are tuned for:
- Resistance to GPU attacks - High memory requirement
- Resistance to ASIC attacks - Memory-hard function
- Usable latency - < 500ms on typical hardware
The master key is stored in the OS secure keychain:
| Platform | Storage Mechanism |
|---|---|
| macOS | Keychain Services (SecItemAdd) |
| Windows | Credential Manager |
| Linux | Secret Service API |
Key properties:
- Protected by OS-level security
- Requires biometric or password to access
- Never written to disk in plaintext
- Cleared from memory after use
Service: dev.moinsen.secretariat.daemon
Account: master_key
Data: [32 bytes - encrypted by keychain]
Access: Requires user authentication (Touch ID/password)
When an application first requests a secret, it's registered with:
┌───────────────────────────────────────────┐
│ Application Identity │
├───────────────────────────────────────────┤
│ app_id: UUID │
│ name: my-python-app │
│ path: /usr/bin/python3 │
│ bundle_id: com.python.python3 │
│ fingerprint: SHA-256 of executable │
│ pid: 1234 (current process) │
└───────────────────────────────────────────┘
Permissions are stored per-app, per-secret:
permissions (
app_id → applications.id
secret_id → secrets.id
granted_at
granted_by -- 'user' or 'auto'
)Permission flow:
App requests secret.get("OPENAI_API_KEY")
│
▼
┌─────────────────────────┐
│ Check permissions │
│ table for (app, key) │
└───────────┬─────────────┘
│
┌───────┴───────┐
│ │
▼ ▼
Granted Denied
│ │
▼ ▼
Decrypt Return error
& return Log attempt
Permission revocation is immediate:
- Delete from permissions table
- No caching of permissions
- Next request will fail
| Event | Fields |
|---|---|
| Secret read | timestamp, app_id, secret_name, success |
| Secret write | timestamp, app_id, secret_name, success |
| Secret delete | timestamp, app_id, secret_name, success |
| Permission grant | timestamp, app_id, secret_name, granted_by |
| Permission revoke | timestamp, app_id, secret_name |
| Failed access | timestamp, app_id, secret_name, reason |
- Secret values (never!)
- Master key material
- Decrypted content
- Default: 30 days
- Configurable per deployment
- Automatic cleanup of old entries
All sensitive data is zeroed from memory after use:
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(ZeroizeOnDrop)]
struct SensitiveBuffer {
key: [u8; 32],
plaintext: Vec<u8>,
}
// Automatically zeroed when dropped- No secret logging - Values never printed
- Short lifetime - Secrets decrypted only when needed
- Explicit zeroing - Using zeroize crate
- Stack allocation - Fixed-size keys on stack when possible
| Threat | Mitigation |
|---|---|
| Disk theft | AES-256-GCM encryption |
| Memory dump | Zeroization after use |
| Network sniffing | Local Unix socket only |
| Unauthorized app | Permission system |
| Tampering | GCM authentication |
| Brute force | Argon2id key derivation |
| Threat | Reason |
|---|---|
| Kernel-level malware | Requires OS compromise |
| Physical keylogger | Beyond software scope |
| User coercion | Social engineering |
| Memory freezing | Requires physical access |
- Use a strong master password - 16+ characters, unique
- Enable Touch ID - Faster and more secure than typing
- Review audit logs - Check for unexpected access
- Revoke unused apps - Minimize attack surface
- Keep software updated - Security patches
- Minimize secret lifetime - Fetch, use, forget
- Don't log secrets - Ever, even in debug mode
- Use SDKs - Don't reimplement the protocol
- Handle errors - Don't expose secret names in UI
- Request minimally - Only request needed secrets
Secretariat's security design supports compliance with:
- SOC 2 - Encryption, access control, audit logging
- GDPR - Data protection, access controls
- PCI DSS - Encryption of sensitive data
- HIPAA - Access controls, audit trails
Note: Compliance depends on overall deployment and usage patterns.
- Security issues: security@secretariat.dev
- Bug bounty: https://secretariat.dev/security
- PGP key: Available on our website