DSO is designed around a single principle: secrets must never touch disk in plaintext. This document explains the security properties of DSO, the trust boundaries, and the guarantees we provide.
DSO is built on three security pillars:
-
Zero-Persistence on Host Storage — Plaintext secrets are never written to the host's physical filesystem. Intermediate data exists only in volatile memory (RAM) or kernel-managed
tmpfsmounts. -
Least Privilege — Secret files are injected with minimum required permissions (
0400read-only) and assigned specificUID/GIDowners to ensure application-level isolation. -
Volatile Secret Lifecycle — Secrets are ephemeral; they are wiped from the host RAM upon provider rotation or agent shutdown.
Most teams running Docker Compose face three concrete risks:
- Git Leaks:
.envfiles get committed. Once in git history, rotation is the only safe recovery. - Process Exposure: Any local process can read an unencrypted file.
docker inspectExposure: Docker stores environment variables in its metadata layer. Anyone with Docker socket access can rundocker inspect <container>and read your passwords in plain text — no breach required.
All three share the same root cause: secrets are plaintext on disk before they reach the container.
DSO solves all three by keeping secrets only in memory and injecting them at container startup.
Single-shot injection, no daemon:
- Load & Decrypt — Read encrypted
~/.dso/vault.enc, decrypt with user's master key (Argon2id) - Parse Compose — Load
docker-compose.yamlfrom disk - Inject Secrets (in-memory only):
dso://secret-name→ environment variable injectiondsofile://secret-name→ tmpfs file injection (recommended)
- Execute — Pass resolved compose file to
docker compose upvia stdin (secrets never hit disk) - Container Runtime — Docker injects secrets into container process
- Cleanup — CLI exits, process memory cleared by OS
Secrets never touch host disk.
Continuous daemon with provider-based rotation:
- Daemon Start — systemd starts
dso-agent, connects to Docker socket - Provider Auth — Load dso.yaml configuration, authenticate with provider (Vault/AWS/Azure)
- Watch Events — Listen for container lifecycle events (
start,stop,die) - Fetch & Cache — On event, fetch secrets from provider over TLS into agent RAM
- Checksum Validation — Compare new secrets against cached version
- Conditional Rotation:
- If changed: stop old container, start new with updated secret
- If unchanged: skip (avoid unnecessary restarts)
- Health Check — Wait for container health check to pass (if configured)
- Cleanup — Remove old container; kernel manages tmpfs cleanup
Secrets are cached in agent RAM; never written to disk.
Local Mode: Secrets stored in ~/.dso/vault.enc using AES-256-GCM authenticated encryption. The encryption key is derived from a machine-specific master key via Argon2id (128 MB memory, 3 iterations), making offline brute-force attacks computationally infeasible.
Cloud Mode: Secrets never stored locally. Fetched from provider via TLS, held in agent RAM only.
When you use dsofile://, DSO mounts a tmpfs RAM disk at /run/secrets/dso/ inside the container and streams the secret via an in-memory tar archive using the Docker API.
- Never writes to host disk (SSD or HDD)
- Disappears if the container stops or the machine reboots
- Invisible to
docker inspect - File permissions:
0400(read-only by owner, no group/world access)
When you use dso://, secrets are injected as environment variables. These are visible to docker inspect. Only use for non-sensitive configuration data or when your application requires environment variable injection.
DSO agent communicates via Unix domain socket (/run/dso/dso.sock). Socket permissions are critical to prevent unauthorized secret access.
Socket Permissions:
- Mode:
0660(read-write for owner and group, no world access) - Owner:
root:dso(root user, dso group) - Access Control: Only
rootand members ofdsogroup can connect
Permission Enforcement:
- At Socket Creation: Socket created with requested mode (
0660if dso group exists, fallback to0600) - Group Lookup: DSO verifies
dsogroup exists at startup; warns if group not found - Chown Operation: Socket and directory
chownd toroot:dsofor non-root access - Chmod Operation: Explicit
chmod 0660ensures no world-readable access - Fallback: If
chmodfails, agent exits with fatal error (refusing to run with unsafe socket)
Threat Model:
- ✅ Protects Against: Non-root users accessing secrets without authorization
- ✅ Protects Against: World-readable socket exposing secrets
⚠️ Does Not Protect: Root user (root can always read secrets from agent RAM)⚠️ Does Not Protect: Host compromise (compromise = full secret access)
Best Practices:
- Always create
dsogroup:sudo groupadd dso - Add users to group:
sudo usermod -aG dso $USER - Verify permissions:
ls -la /run/dso/dso.sock(should showroot dso 0660) - Monitor socket:
docker dso doctorreports socket status
All DSO output passes through a redaction engine, masking:
- API keys (
api_key=,sk-*) - Tokens (
token=,authorization:) - Passwords and secrets
- Provider credentials
Secrets will not appear in logs, even when operations fail.
TTL-based cache prevents repeated processing of same event, reducing the exposure window for secrets in agent RAM.
Crash detection and health monitoring prevents cascade failures. v3.5 includes circuit breaker pattern for provider failure isolation.
- Detects incomplete rotations on startup
- Cleans up orphaned containers automatically
- Validates original container state before recovery
- Handles critical errors with operator alerts
DSO assumes the Docker daemon is secure, uncompromised, and governed by appropriate access controls.
- If Docker is compromised: DSO secrets are compromised (Docker has full host access)
- Recommendation: Use Docker socket ACLs to restrict access to
dso-agentprocess
DSO assumes host kernel is trusted and protects DSO process memory.
- In Cloud Mode: Agent RAM contains cached secrets; kernel page isolation provides protection
- An attacker with
root: Can read DSO agent memory ordocker execinto any container - Recommendation: Restrict
sudoaccess; use kernel LSM (AppArmor/SELinux) if available
DSO assumes providers (Vault, AWS, Azure) are reachable over TLS and haven't been compromised.
- Provider credentials: Stored in
dso.yamlon host filesystem (protect like/etc/passwd) - Recommendation: Use provider IAM roles where available (IAM role on EC2, managed identity on Azure, Kubernetes service account for Vault)
DSO provides explicit security properties for production deployments:
Guarantee: Secrets, tokens, API keys, and credentials do NOT appear in DSO logs, even when operations fail.
Scope: Provider initialization errors, daemon reconnection failures, RPC errors, timeout context, nested error chains, stack traces.
Validation: All error logging passes through redaction engine. Patterns detected and redacted: API keys, tokens, Bearer auth, provider credentials.
Guarantee: Event deduplication prevents duplicate application of operations.
Validation: TTL-based cache ensures each secret change is processed once per deduplication window.
Guarantee: Provider failure doesn't cascade to other providers.
Implementation: Circuit breaker pattern isolates provider failures. States: Closed (normal), Open (rejecting), Half-open (testing). Auto-recovery with configurable thresholds.
Validation: Circuit breaker status visible in docker dso status.
Guarantee: Long-duration deployments don't exhibit unbounded memory growth.
Validation: Agent memory usage stabilizes after initial setup. Goroutine leaks detected in race-condition testing.
Guarantee: Goroutines are properly cleaned up on shutdown.
Validation: Resource stability testing validates goroutine cleanup. No unbounded growth over time.
Guarantee: Agent crashes during rotation do not leave containers in inconsistent state.
Implementation:
- On startup: Detects pending rotations older than 5 minutes
- Auto-rollback: Cleans up orphaned containers using naming patterns (
_dso_backup_,_dso_new_) - Validation: Original container verified running after recovery
- Fallback: Critical errors marked for operator review
Validation: Recovery procedures documented. Manual recovery available for edge cases.
# docker-compose.yaml
services:
db:
image: postgres:15
environment:
# RECOMMENDED: Use dsofile:// for file-based injection
POSTGRES_PASSWORD_FILE: dsofile://db/password
# NOT RECOMMENDED: dso:// is visible to docker inspect
# POSTGRES_PASSWORD: dso://db/passwordRun with: docker dso up -d
# /etc/dso/dso.yaml
providers:
vault:
type: vault
# Use IAM roles instead of hardcoded credentials where possible
auth:
method: iam_role # On AWS EC2, uses instance IAM role
rotation:
strategy: restart # Gracefully stop/start containers
grace_period: 30s
logging:
level: info # Prevents secret leaks in debug tracesDSO does not provide protection against these threats:
| Threat | Protection | Mitigation |
|---|---|---|
| Container Compromise | ❌ None | If attacker runs code in container, they can read injected secrets |
| Root Access | ❌ None | root can read DSO agent RAM, inspect containers, access dso.yaml |
| Docker Socket Exposure | ❌ None | If /var/run/docker.sock is readable by untrusted user, they can bypass DSO |
| Insecure Provider | ❌ None | If provider backend is compromised, DSO can't help |
| Misconfigured dso.yaml | ❌ None | Wrong routing of secrets to containers |
| Memory Scraping | Privileged attacker can dump agent RAM on Cloud Mode |
| Feature | .env files |
dso:// |
dsofile:// |
|---|---|---|---|
| Stored encrypted at rest | ❌ No | ✅ Yes (local vault) | ✅ Yes (local vault) |
Visible to docker inspect |
✅ Yes (exposed) | ✅ Yes (env vars) | ❌ No (tmpfs) |
| Written to host disk at runtime | ✅ Yes (risk) | ❌ No | ❌ No |
| Git leak risk | ✅ High | ❌ None | ❌ None |
| Survives container restart | ✅ Yes | ✅ Yes (re-injected) | ✅ Yes (re-injected) |
| Requires host filesystem | ✅ Yes | ❌ No | ❌ No |
Recommendation: Always prefer dsofile:// for production workloads.
We take security seriously. If you find a vulnerability, please report it responsibly:
- Do NOT create a public GitHub issue
- Email: security@docker-secret-operator.org
- Follow: Please allow 48 hours for acknowledgment and ~14 days for a fix before public disclosure
- THREAT_MODEL.md — Detailed threat analysis
- docs/PERSISTENCE_MODEL.md — What data DSO persists and where
- docs/RECOVERY_PROCEDURES.md — Recovery from failures