dracon-warden
Git filter + repo hardening tool. Encrypts secrets at rest in git while keeping plaintext in your working tree. Uses git hooks (not a daemon) as the primary enforcement layer.
Install
The binary will be at ~/.cargo/bin/dracon-warden. Or install from the long-name façade repo:
Mental Model (Important)
- Working tree is plaintext:
filter.smudgedecrypts so your app can read normal config/secrets. - Git blobs are ciphertext:
filter.cleanencrypts so secrets are encrypted-at-rest in history.
To verify what is stored in git (not your working tree), use:
If encryption is active for that path, you should see marker payloads like [DRACON_SECRET:...]
in the git show output (even though your working tree file is plaintext).
Features
Age-Based Encryption
- Uses age encryption with x25519 keys
- Secrets encrypted with per-repo keys
- Team key distribution for collaboration
- Master key hierarchy for key recovery
Secret Scanning
- Comprehensive regex patterns for AWS, GCP, Azure, GitHub, Slack, etc.
- Scans for API keys, tokens, passwords, private keys
- Configurable allowlists for legitimate plaintext patterns
- Prevents accidental secret exposure in git history
Clean/Smudge Filter Pipeline
filter.clean: Encrypts secrets when staging filesfilter.smudge: Decrypts secrets when checking out files- Idempotent operations (safe to run multiple times)
- Handles binary files, large files, already-encrypted content
Repo Hardening
- Sets up git filter configuration
- Publishes repo public keys
- Manages
.gitattributesfor encryption patterns - Creates encryption manifests
Team Collaboration
- Owner keys for repo authorization
- Team keys for shared access
- Registry credentials management
- Key rotation support
Plaintext-Sibling Escape Hatch (Opt-In)
- Some files contain values that should never be encrypted (public example keys, fixture data, benchmark datasets)
- Touch a
<file>.plaintextsibling to opt a specific file in to plaintext storage — the clean filter returns it unchanged, the pre-push hook silently skips it - Revocation:
rm <file>.plaintextand the next commit re-encrypts - The hatch is per-file; the rest of the repo is unaffected
- See
docs/design/warden-plaintext-sibling.mdfor threat model and what the hatch does NOT protect against - Default install behaviour is unchanged: no
.plaintextsibling → encryption
Installation
Quick Install
Run the repository installer from the repository root:
This will:
- Build the release binary
- Install to
~/.local/bin/dracon-warden - Install git hooks globally via
dracon-warden setup-hooks --global
The per-utility directories do not contain standalone installers; use the root install.sh for all utilities.
Manual Install
# Build
# Copy binary
# Install git hooks globally
Usage
Commands
# Show resolved policy path and repo roots
# Run one hardening pass and exit
# Generate new age keypair
# Git filter operations (used by git automatically)
# Recovery tools
# Fix ciphertext stuck in working tree
# System-wide repair pass
# Install git hooks globally (primary enforcement layer)
Configuration
Create ~/.dracon/utilities/warden/dracon-warden.toml:
# Directories to scan for git repos (canonical field)
= ["/home/user/Dev"]
# Additional discovery roots (optional; if omitted, repo_roots is used)
= ["/home/user/Dev"]
# Exclude specific directories
= ["node_modules", "target", ".venv"]
# Plaintext patterns (files that must remain plaintext in git)
# WARNING: Must not include secret-ish patterns like .env or secrets/**
= [
"*.lock",
"*.pub",
"Cargo.lock",
"package-lock.json",
"yarn.lock",
"pnpm-lock.yaml",
]
# Secret marker (default: DRACON_SECRET)
= "DRACON_SECRET"
# Encryption version (1 or 2)
= 2
# Allow V1 fallback (for migration)
= false
# Team keys (for shared access)
= [
"age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
]
# Registry credentials
[[]]
= "ghcr.io"
= "username"
# Password stored in secrets file
# Owner key (for repo authorization)
= "~/.dracon/keys/owner.age"
Safety Defaults
plaintext_patternsis for files that must remain plaintext in git (lockfiles, public keys, etc).plaintext_patternsmust not include secret-ish patterns (like.envorsecrets/**). dracon-warden will refuse to run if the policy tries to disable encryption for those.
Key Management
Key Hierarchy
~/.dracon/identity.age — Master x25519 private key
~/.dracon/master.age — Sovereign master key
~/.dracon/keys/*.age — Additional identities
~/.dracon/data/keys/machine_*.age — Machine-level secret keys
~/.dracon/data/keys/owner_*.pub — Owner key for repo authorization
Key Generation
# Generate new age keypair
# Keypair saved to:
# - ~/.dracon/keys/machine_<hostname>.age (private)
# - ~/.dracon/keys/machine_<hostname>.age.pub (public)
Team Keys
Team keys allow multiple users to access the same encrypted secrets:
- Each user generates their own keypair
- Public keys are added to the repo's team keys list
- Secrets are encrypted to all team keys
- Any team member can decrypt secrets
How It Works
Encryption Flow
- User edits
.envfile (plaintext in working tree) git addtriggersfilter.clean- dracon-warden scans for secrets
- Secrets are encrypted with age encryption
- Encrypted content stored as
[DRACON_SECRET:base64_age_ciphertext] - Commit contains encrypted blobs
Decryption Flow
git checkouttriggersfilter.smudge- dracon-warden detects encrypted markers
- Secrets are decrypted with local private key
- Plaintext written to working tree
- App reads normal
.envfile
Secret Detection
dracon-warden scans for:
- AWS access keys, secret keys, session tokens
- GCP API keys, OAuth tokens, service accounts
- Azure storage keys, shared access signatures
- GitHub tokens, SSH keys
- Slack webhooks, bot tokens
- Database connection strings
- Private keys (RSA, EC, ED25519)
- And many more patterns
Recovery Tools
scrub-markers
Fixes cases where marker tokens accidentally land in plaintext JSON:
# Scan for markers
# Fix markers
resmudge
Fixes ciphertext stuck in working tree:
# Dry run
# Apply fixes
repair
System-wide repair pass:
# Dry run
# Apply fixes
# Strict mode (more checks)
Security Considerations
What's Encrypted
.envfiles- Files matching
secret_patternsin policy - Files containing detected secrets
What's NOT Encrypted
- Files matching
plaintext_patternsin policy - Lock files (Cargo.lock, package-lock.json)
- Public keys (*.pub)
- Configuration files without secrets
Key Storage
- Private keys stored in
~/.dracon/ - Keys are never committed to git
- Backup your keys! Loss means permanent data loss
Version