██████╗ ██████╗ ████████╗ ██╗ ██████╗ ██████╗ ██╗ ██╗
██╔══██╗ ██╔═══██╗ ╚══██╔══╝ ██║ ██╔═══██╗ ██╔════╝ ██║ ██╔╝
██║ ██║ ██║ ██║ ██║ ██║ ██║ ██║ ██║ █████╔╝
██║ ██║ ██║ ██║ ██║ ██║ ██║ ██║ ██║ ██╔═██╗
██████╔╝ ╚██████╔╝ ██║ ███████╗ ╚██████╔╝ ╚██████╗ ██║ ██╗
╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
DotLock is a local, file-based secrets manager for project environment variables. It stores secrets in a .lock/ vault next to your code and injects decrypted values only into child processes launched with dl run.
Use DotLock when you want:
- to keep
.envvalues encrypted in a repository; - to run local commands with decrypted variables without writing plaintext back to disk;
- to share project secrets with teammates through public keys instead of a shared password;
- to keep a lightweight audit log of unlocks, runs, key ratchets and dynamic provider resolutions.
DotLock is not a hosted production secret manager. For large production systems, prefer a dedicated service such as AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault or your platform's native secret store.
DotLock is alpha software. The cryptographic building blocks are conservative, but the on-disk format and CLI surface may still change before 1.0. Keep backups of .lock/ and review changes before upgrading across 0.1.x releases.
cargo install dotlock-binThe crate is named dotlock-bin; the installed binary is named dl.
cargo build --release
install -m 0755 target/release/dl ~/.local/bin/dlDotLock uses Rust edition 2024.
cd my-project
dl init
dl migrate .env
dl run npm startWhat happens:
dl initcreates.lock/vault.tomland.lock/secrets.lock.dl migrate .envimports variables from an existing.envfile.dl run npm startstartsnpm startwith decrypted secrets in the child process environment.
After migration, remove the plaintext .env only when you have verified the imported values:
dl list
dl get DATABASE_URL
rm .envWhat: dl init creates the project vault in the current directory.
How:
dl initYou will choose whether DotLock generates a strong master password or lets you type one. The vault metadata goes into .lock/vault.toml; encrypted secret records go into .lock/secrets.lock.
When: Run once per project before using any other project command.
What: dl set NAME VALUE stores a secret value under a normalized environment variable name.
How:
dl set DATABASE_URL "postgres://localhost/app_dev"
dl set STRIPE_KEY "sk_live_..."
dl set API_KEY "secret" --alg xchaAliases:
dl s DATABASE_URL "postgres://localhost/app_dev"
dl add DATABASE_URL "postgres://localhost/app_dev"Names must use letters, digits and underscores, and cannot start with a digit. DotLock normalizes names to uppercase.
--alg (short -a) selects the encryption algorithm for static secrets. The current supported value is xcha, which maps to XChaCha20-Poly1305.
When: Use this when adding a new secret or replacing an existing value.
What: dl get NAME decrypts one secret and prints it.
How:
dl get DATABASE_URL
dl get DATABASE_URL | pbcopyWhen stdout is a terminal, DotLock renders a boxed display with the secret name and short id. When stdout is piped, it prints the raw value.
When: Use sparingly for copying or debugging a single value. Prefer dl run for normal command execution.
What: dl list shows stored secret names and short ids without plaintext values.
How:
dl list
dl lWhen: Use before get, unset, ACL changes or audits to confirm what is stored.
What: dl unset NAME removes a secret and its access wrapping metadata.
How:
dl unset OLD_FEATURE_FLAG
dl rm OLD_FEATURE_FLAGOther aliases: remove, u, d, del, delete.
When: Use when a variable is no longer needed or should no longer be available to dl run.
What: dl run CMD [args...] decrypts accessible secrets and starts a child process with those values in its environment.
How:
dl run npm start
dl run python manage.py runserver
dl r cargo testThe -- separator is important when the command has its own flags.
When: Use this as the normal way to consume secrets locally or in scripts.
What: dl lock deletes the cached project key for the current project.
How:
dl lock
dl logoutWhen: Use after a sensitive session, before handing off a machine, or at the end of CI/scripted workflows.
What: dl migrate [PATH] imports variables from a .env file into the encrypted vault.
How:
dl migrate
dl migrate .env.local
dl import .env.productionThe parser accepts:
KEY=VALUE;export KEY=VALUE;- single-quoted values;
- double-quoted values with escapes such as
\n,\r,\t,\\and\"; - comments and blank lines.
When: Use when adopting DotLock in an existing project or bulk-loading variables.
What: dl export [PATH] writes missing static secrets into a .env file.
How:
dl export
dl export .env.localExisting keys are never overwritten. Dynamic secrets are skipped because their values are resolved at runtime.
When: Use for tools that still require a .env file, or when bridging DotLock with legacy local workflows.
DotLock supports shared access through a local RSA identity. The project owner keeps the master password; recipients unlock with their own private key.
What: dl cert init creates a local RSA key pair.
How:
dl cert init
dl cert init --plain
dl cert init --forceAliases:
dl crt init
dl crt iBy default, the private key is passphrase-protected. --plain creates an unencrypted private key. --force replaces an existing identity.
When: Each developer or CI environment should do this once before requesting shared access.
What: dl cert show prints the local identity fingerprint and paths. dl cert export-pub [PATH] prints or writes the public key.
How:
dl cert show
dl cert export-pub alice.pub
dl cert export-pubAliases: dl crt sh, dl crt x.
When: Use show to confirm which identity is active. Use export-pub when sending a public key to a vault owner.
What: dl share enable marks the vault as shared.
How:
dl share enable
dl shr enWhen: Use before granting access to teammates or CI.
What: dl share grant adds or updates a recipient.
How:
dl share grant --pubkey alice.pub --label alice
dl share grant --pubkey ci.pub --label ci --allow DATABASE_URL,REDIS_URLAliases: dl shr gr.
Without --allow, the recipient gets full access. With --allow, the recipient can decrypt only the listed secrets.
When: Use when onboarding a developer, adding a CI identity, or changing a recipient's access scope.
What: dl share allow lists, adds or removes per-secret access for a recipient.
How:
dl share allow ci --list
dl share allow ci --add STRIPE_KEY
dl share allow ci --remove REDIS_URLThe recipient query can be the recipient id, label or public key fingerprint. Removing a secret from a recipient rotates that secret's SDK so the old recipient wrapping cannot decrypt future values.
When: Use for least-privilege access, especially for CI identities.
What: dl share list shows recipients. dl share revoke QUERY removes a recipient and rotates project access material.
How:
dl share list
dl share revoke aliceAliases: dl shr l, dl shr rev.
When: Use list before access reviews. Use revoke when someone leaves the project or an identity is compromised.
What: dl rotate master-password changes the password used to unlock the project key.
How:
dl rotate master-password
dl rotate mpWhen: Use when a password may have been exposed but the encrypted vault itself does not need a full project-key rotation.
What: dl rotate kek rotates the key wrapping layer and rewraps secret SDKs and full-access recipient project keys.
How:
dl rotate kekWhen: Use as hygiene after many writes, or let DotLock trigger it automatically with auto_ratchet_after_writes.
What: dl rotate project-key generates a new project key and rewrites encrypted secret records.
How:
dl rotate project-key
dl rotate pkWhen: Use after a suspected project-key exposure or after major access changes that justify a heavier rotation.
Dynamic secrets are resolved by external executables instead of storing a fixed plaintext value. Provider binaries must be named dotlock-provider-NAME and be available on PATH.
What: dl provider list lists provider binaries found on PATH. dl provider info NAME runs a provider's --describe command.
How:
dl provider list
dl provider info aws
dl p list
dl p info awsWhen: Use before creating a dynamic secret, or when debugging provider installation.
What: dl set NAME --provider PROVIDER stores encrypted provider metadata instead of a static value.
How:
dl set DATABASE_URL --provider aws --config '{"name":"prod/db/url"}'
dl set API_TOKEN --provider vault --config '{"path":"secret/api"}' --bootstrap VAULT_TOKEN--config must be valid JSON. --bootstrap is a comma-separated list of existing static secrets that DotLock decrypts and passes to the provider at resolve time.
When: Use when the value should be minted or fetched just-in-time from another local provider, while DotLock still controls bootstrap material and runtime injection.
Providers are executed as child processes:
dotlock-provider-NAME --describeprints human-readable provider information.dotlock-provider-NAME --resolvereads JSON on stdin and prints the resolved secret value on stdout.
The resolve input has this shape:
{
"config": {},
"bootstrap": {
"VAULT_TOKEN": "decrypted bootstrap value"
}
}DotLock records the provider path and SHA-256 digest when the dynamic secret is created. Later resolutions verify the digest before running the provider. Provider stdout is limited to 64 KiB and stderr to 16 KiB.
What: dl git install-merge-driver configures Git to use DotLock's merge driver for .lock/secrets.lock and .lock/vault.toml.
How:
dl git install-merge-driverdl init also attempts to install the merge driver when the project is inside a Git work tree.
When: Use in repositories where multiple branches or teammates may edit the vault.
What: dl sync fetches the configured remote and updates the current branch only when Git can fast-forward safely.
How:
dl sync
dl sydl sync uses auto_fetch_remote from project config, defaulting to origin. It aborts when .lock/vault.toml or .lock/secrets.lock has local staged, unstaged, or untracked changes, or when the local and remote branches diverged. It never runs a hard reset and never discards local vault data.
When: Use before reading, exporting, or rotating secrets when you want to force a remote refresh without enabling auto-fetch for every dl run.
What: when enabled, dl run tries a short git pull --ff-only --no-rebase REMOTE BRANCH before unlocking.
How:
dl config set auto_fetch_on_run true
dl config set auto_fetch_timeout_secs 3
dl config set auto_fetch_remote origin
DOTLOCK_AUTO_FETCH=0 dl run npm testIf pull fails, DotLock tries git fetch and then continues with the local vault.
When: Use when teams commit .lock/ changes frequently and want dl run to pick up fast-forward updates automatically.
DotLock keeps a local audit log outside the project by default. Entries are hash-chained. If a local plain identity is available, entries are signed; otherwise they are anonymous.
What: dl audit show prints audit entries.
How:
dl audit show
dl audit show --verbose
dl audit show --since 2026-05-01
dl audit show --action runAliases: dl audit s, dl a show.
When: Use during local review, debugging or incident response.
What: dl audit verify checks the hash chain and signatures. dl audit rotate compresses the current log with gzip. dl audit path prints the current log path.
How:
dl audit verify
dl audit verify --strict
dl audit path
dl audit rotate--strict rejects anonymous entries. Logs rotate automatically when they reach 10 MiB or are older than 90 days.
When: Use verify before trusting audit history. Use rotate for manual cleanup or archival.
Project configuration is stored in .lock/vault.toml.
dl config show
dl config set auto_fetch_on_run true
dl config set auto_fetch_timeout_secs 3
dl config set auto_fetch_remote origin
dl config set auto_ratchet_after_writes 50
dl config set dynamic_resolve_timeout_secs 10
dl config unset auto_fetch_remoteAliases: dl c show, dl c set, dl c unset.
| Key | Default | Accepted values | Effect |
|---|---|---|---|
auto_fetch_on_run |
false |
true, false, 1, 0, yes, no, on, off |
Enables Git auto-fetch before dl run |
auto_fetch_timeout_secs |
3 |
positive integer | Timeout for Git auto-fetch operations |
auto_fetch_remote |
origin |
non-empty string | Remote used by auto-fetch |
auto_ratchet_after_writes |
off | non-negative integer | Automatically rotates key wrapping after this many vault writes; 0 disables it |
dynamic_resolve_timeout_secs |
10 |
positive integer | Timeout for dynamic provider resolution |
| Variable | Default | Effect |
|---|---|---|
DOTLOCK_CACHE_TTL |
30 |
Cached project key lifetime in seconds, capped at 3600 |
DOTLOCK_CACHE_DIR |
$HOME/.lock on Unix, %LOCALAPPDATA%\dotlock on Windows |
Overrides the session cache root |
DOTLOCK_SHARED_CACHE |
off | Set to 1, true, TRUE, yes or YES to enable caching in shared mode |
DOTLOCK_IDENTITY_DIR |
$HOME/.lock/identity |
Overrides local identity storage |
DOTLOCK_AUDIT_DIR |
$HOME/.lock/audit on Unix, %LOCALAPPDATA%\dotlock\audit on Windows |
Overrides audit log storage |
DOTLOCK_AUTO_FETCH |
unset | Set to 0, false, no or off to disable auto-fetch for one command |
| Command | Aliases | Purpose |
|---|---|---|
dl init |
i |
Initialize .lock/ in the current project |
dl set [--alg xcha] NAME VALUE |
s |
Store or update a static secret |
dl set NAME --provider PROVIDER [--config JSON] [--bootstrap A,B] |
s |
Store a dynamic secret definition |
dl get NAME |
g |
Print a secret value |
dl unset NAME |
u |
Remove a secret |
dl list |
l |
List stored secrets without plaintext |
dl run CMD [args...] |
r |
Run a command with decrypted secrets in its environment |
dl lock |
k |
Drop the cached project key |
dl migrate [PATH] |
m |
Import variables from .env |
dl export [PATH] |
x |
Append missing static secrets to .env |
dl sync |
sy |
Fast-forward from the configured Git remote when safe |
dl cert init [--force] [--plain] |
crt init, crt i |
Create a local RSA identity |
dl cert show |
crt show, crt sh |
Show local identity information |
dl cert export-pub [PATH] |
crt export-pub, crt x |
Print or write the public key |
dl share enable |
shr enable, shr en |
Enable shared mode |
dl share grant --pubkey FILE --label LABEL [--allow A,B] |
shr grant, shr gr |
Grant recipient access |
dl share allow QUERY --list |
shr allow, shr al |
List recipient ACL |
dl share allow QUERY --add A,B |
shr allow, shr al |
Add secrets to recipient ACL |
dl share allow QUERY --remove A,B |
shr allow, shr al |
Remove secrets from recipient ACL and rotate affected SDKs |
dl share revoke QUERY |
shr revoke, shr rev |
Revoke a recipient |
dl share list |
shr list, shr l |
List recipients |
dl rotate kek |
rot kek, rot k |
Rotate key wrapping material |
dl rotate master-password |
rot master-password, rot mp |
Change master password |
dl rotate project-key |
rot project-key, rot pk |
Rotate project key |
dl audit show [--verbose] [--since YYYY-MM-DD] [--action ACTION] |
a show, audit s |
Show audit entries |
dl audit verify [--strict] |
a verify, audit v |
Verify audit log |
dl audit path |
a path, audit p |
Print audit log path |
dl audit rotate |
a rotate, audit r |
Rotate and gzip current audit log |
dl git install-merge-driver |
gt install-merge-driver, gt i |
Configure Git merge support |
dl config show |
c show, config sh |
Show project config |
dl config set KEY VALUE |
c set, config s |
Set project config |
dl config unset KEY |
c unset, config u |
Reset project config |
dl provider list |
p list, provider l |
List provider binaries on PATH |
dl provider info NAME |
p info, provider i |
Show provider description |
Compatibility aliases such as add, rm, remove, del, delete, logout, and import remain available, but the table above is the canonical long/short command set.
Project vault:
.lock/
├── vault.toml
└── secrets.lock
Local user state:
~/.lock/
├── identity/
│ ├── identity.pem
│ ├── identity.pub.pem
│ └── identity.toml
├── run/sessions/<project>/
│ └── sessions.toml
└── audit/<project-uuid>/
└── audit.log
On Unix, private directories are created with 0700, secret/private files with 0600, and public key files with 0644. Writes use an atomic temp-file-and-rename flow.
New secrets.lock records store the secret id, variable name, ciphertext data, update timestamp, and kind. They do not store the data encryption algorithm next to the variable name. DotLock uses the storage-version cipher policy, currently XChaCha20-Poly1305, and still reads older records that contain alg = "xchacha20-poly1305".
Keeping the algorithm out of each visible secret record removes a per-secret cryptographic hint from the Git-tracked vault. An attacker who obtains .lock/ can still see variable names and ciphertexts, but cannot use a name-to-algorithm pairing to classify records, prioritize guesses, or tune offline brute-force workflows per secret. Algorithm agility remains a vault/storage-version concern rather than secret metadata.
master password
-> Argon2id derives master key
-> HKDF derives KEK
-> KEK wraps project key
-> project key wraps per-secret SDKs
-> SDK encrypts each secret value or dynamic provider metadata
Terms:
- Master password: typed or generated during
dl init. - Master key: derived from the password and salt; held briefly and zeroized.
- KEK: key-encryption key derived for wrapping the project key.
- Project key: random 32-byte key stored only in wrapped form.
- SDK: per-secret data key used to encrypt one secret's stored data.
This is why changing a master password is fast, while project-key and ACL rotations touch more vault metadata.
Every write to secrets.lock updates an authenticated hash in vault.toml. On unlock, DotLock verifies the hash before decrypting secrets. If secrets.lock was modified outside DotLock, the command aborts with a tamper error.
After successful unlock, DotLock may cache the project key for a short time. The default TTL is 30 seconds. Shared mode disables caching unless DOTLOCK_SHARED_CACHE is enabled.
Full-access recipients receive wrapped access to the project key and per-secret SDKs. Limited recipients receive only wrapped SDKs for allowed secrets. Removing a secret from a recipient ACL rotates the affected SDKs.
dl getprints plaintext. Preferdl runwhen a command can consume secrets directly.- Do not commit plaintext
.envfiles after migrating to DotLock. - Commit
.lock/vault.tomland.lock/secrets.lockif your team shares the encrypted vault through Git. - Use
dl syncto refresh the vault safely from Git. It aborts on local vault changes or branch divergence instead of discarding data. - Keep local identity private keys protected. Use
dl cert init --plainonly for controlled automation or ephemeral environments. - Dynamic providers are executable code. Install them from trusted sources and keep provider directories non-world-writable.
- Audit logs are local by default. They are useful for local accountability and debugging, not a centralized compliance system.
Dual-licensed under your choice of:
- MIT: LICENSE-MIT
- Apache 2.0: LICENSE-APACHE