A CLI tool to bulk-encrypt, decrypt, and place multiple secret files across a project using age encryption and a simple YAML mapping config.
Projects often contain multiple secret files scattered across directories — API keys, certificates, Firebase configs, and more. agedir manages them all through a single agedir.yaml configuration, letting you:
- Encrypt raw files into a storage directory with one command
- Decrypt and place them back to their original paths with one command
- Rekey all encrypted files when team members join or leave
- Init a project by auto-detecting common secret file patterns
agedir ships as a single statically-linked binary (no external age installation required) and runs on Windows, macOS, and Linux.
brew install asmz/tap/agedircurl -sSL https://github.com/asmz/agedir/releases/latest/download/install.sh | shTo pin a specific version (recommended for CI):
export AGEDIR_VERSION=v1.0.0
curl -sSL https://github.com/asmz/agedir/releases/download/${AGEDIR_VERSION}/install.sh | shExample for GitHub Actions:
- name: Install agedir
env:
AGEDIR_VERSION: v1.0.0
run: curl -sSL "https://github.com/asmz/agedir/releases/download/${AGEDIR_VERSION}/install.sh" | shgo install github.com/asmz/agedir@latestDownload the latest binary for your platform from the Releases page.
git clone https://github.com/asmz/agedir.git
cd agedir
make buildRequires Go 1.26 or later.
Each team member needs an age key pair. Install age and generate one:
# macOS
brew install age
# other platforms: https://github.com/FiloSottile/age#installation
age-keygen -o ~/.age/key.txt
# Public key: age1xxxx...xxxxShare the public key (age1...) with your team to add to agedir.yaml. Keep the private key file (~/.age/key.txt) secret and never commit it.
For more details, see the age documentation.
agedir initScans the current directory for common secret file patterns and generates an agedir.yaml template. Detected file paths are automatically appended to .gitignore.
Add your team's age public keys to recipients:
version: "1"
recipients:
- age1xxxx...xxxx # alice
- age1yyyy...yyyy # bob
storage_dir: .agedir/secrets
mapping:
- raw: android/app/google-services.json
enc: google-services.json.age
- raw: ios/Runner/GoogleService-Info.plist
enc: GoogleService-Info.plist.ageagedir encryptReads each raw file and writes the age-encrypted output to storage_dir/enc. Commit the storage_dir contents to your repository.
agedir decrypt -i ~/.age/key.txtDecrypts each encrypted file and places it at the configured raw path. Run this when setting up a new environment.
Scan the project and generate an initial agedir.yaml.
agedir init [--config agedir.yaml]Prompts for confirmation before overwriting an existing config.
Encrypt all raw files according to the config.
agedir encrypt [--config agedir.yaml] [--dry-run]Decrypt all encrypted files according to the config.
agedir decrypt [--identity|-i <keyfile>] [--passphrase|-p] \
[--verify] [--dry-run] [--config agedir.yaml]| Flag | Description |
|---|---|
-i, --identity |
Path to age private key file |
-p, --passphrase |
Decrypt using passphrase (reads from AGEDIR_PASSPHRASE env or terminal prompt) |
--verify |
Skip writing if the existing file's SHA-256 hash matches |
--dry-run |
Print files to be processed without writing anything |
--config |
Path to agedir.yaml (default: current directory) |
Re-encrypt all files with the current recipients list.
agedir rekey [--config agedir.yaml]Use this after adding or removing team members from recipients.
Note:
rekeyreads the raw (decrypted) files to re-encrypt them. The raw files must be present on disk before runningrekey. If raw files have been deleted, decrypt them first withagedir decrypt.
agedir.yaml schema:
version: "1" # reserved for future schema migrations; always set to "1"
recipients: # required for public key mode; omit or leave empty for passphrase mode (-p)
- age1...
storage_dir: .agedir/secrets # optional; default: .agedir/secrets
mapping: # required; one or more raw/enc pairs
- raw: path/to/secret.txt # path relative to project root (original file)
enc: secret.txt.age # path relative to storage_dir (encrypted file)agedir decrypt resolves the decryption identity in the following order:
--identityflag (private key file path)AGEDIR_IDENTITYenvironment variable (private key file path)AGEDIR_PASSPHRASEenvironment variable (passphrase, when-pis set)- Interactive terminal prompt (passphrase, when
-pis set)
- Raw files (
rawpaths) are added to.gitignorebyagedir init— never commit them. - Encrypted files (
storage_dir) are safe to commit. - Passphrases are never passed as command-line arguments (avoids exposure via
ps). - Encrypted files are written atomically (temp file + rename) to prevent corruption on interruption.
AGEDIR_PASSPHRASEenvironment variable: Convenient for scripting but carries risk — environment variables may be visible in process listings or captured in CI/CD logs. Prefer--identitywith a key file in automated environments.
agedir init detects the following file patterns:
| Pattern | Example |
|---|---|
*.jks, *.keystore |
Android Keystore |
*.p8 |
PKCS#8 certificate |
*.p12 |
PKCS#12 certificate |
google-services*.json |
Firebase Android config |
GoogleService-Info*.plist |
Firebase iOS config |
*.pem, *.key |
TLS certificates / private keys |
Directories are excluded from scanning at the directory level, not the file level:
- git-ignored directories: If a directory is ignored by git (via any
.gitignorein the repository), it is skipped entirely along with all its contents. This prevents third-party package directories such asvendor/,node_modules/, andPods/from polluting the results. - Built-in fallback list: When git is unavailable, the following directories are excluded by default:
node_modules/,.bundle/,Pods/,.dart_tool/.
Note: Individual files that are git-ignored are still detected as long as their parent directory is not git-ignored. This is intentional — raw secret files are typically gitignored at the file level, and
agedir initis designed to find and manage exactly those files.If you store secrets inside a git-ignored directory (e.g., a
secrets/directory that is itself gitignored), those files will not be detected automatically. Add them toagedir.yamlmanually.
no identity specified; use --identity, -p, or set AGEDIR_IDENTITY
No decryption identity was provided. Pass -i ~/.age/key.txt, use -p for passphrase mode, or set the AGEDIR_IDENTITY environment variable.
no matching identity found (key mismatch)
The private key does not match any of the recipients the file was encrypted for. Make sure you are using the correct key, or ask a team member to run agedir rekey after adding your public key to recipients.
agedir.yaml not found
Run agedir init to create an initial configuration, or use --config to point to the correct path.
warning: raw file not found: ... (during encrypt/rekey)
The file listed in mapping[].raw does not exist. Ensure the file is present before encrypting, or remove the entry from agedir.yaml.
warning: encrypted file not found: ... (during decrypt)
The encrypted file is missing from storage_dir. Run agedir encrypt to generate it, or pull the latest version from your repository.
Passphrase prompt appears multiple times
This should not happen in normal usage. If it does, ensure you are using -p and not mixing identity and passphrase flags.
make cross-buildProduces binaries for darwin/amd64, darwin/arm64, linux/amd64, linux/arm64, windows/amd64, and windows/arm64 in the dist/ directory. All builds use CGO_ENABLED=0 for pure Go static linking.
MIT — see LICENSE.