lock is a sudo-gated local secret vault for developer machines.
It reduces the blast radius of supply-chain attacks where code running as your normal user silently reads local secrets (.env, cloud credentials, npm tokens, deploy keys, etc.). Protected files are encrypted in place at their original paths. Commands that need plaintext must be launched through lock exec, lock run, or lock pipe.
Rust core/CLI/daemon
crates/lock-core age-compatible encryption, metadata, audit
crates/lock-cli lock CLI
crates/lockd Unix socket daemon MVP
TypeScript UI
ui/ OpenTUI manager that shells out to lock- Rust toolchain
age+age-keygensudo- Bun for the OpenTUI manager
cargo buildcargo install --path crates/lock-cli --bin lock --locked
cargo install --path crates/lockd --bin lockd --lockedOr install both binaries, completions, and the man page under /usr/local:
make installBuild a tarball and SHA-256 checksum under dist/:
make packagePackaged installs include the TUI source under share/lock/ui. Before using lock tui from a packaged install, install its Bun/npm dependencies once:
cd /usr/local/share/lock/ui
npm installBlack-box integration tests are written in Bats so the test suite stays independent from the Rust implementation.
bats tests/batsThe tests use isolated HOME/XDG directories and a fake sudo wrapper that redirects /etc/lock/identity.txt to a temporary age identity. They do not require touching the real /etc/lock.
For a real sudo smoke test on a clean machine after install:
scripts/smoke-real-sudo.shSet LOCK_BIN=/path/to/lock to test a non-PATH binary.
cargo run -p lock-cli -- init
cargo run -p lock-cli -- protect aws ~/.aws/credentials
cargo run -p lock-cli -- protect env .env
cargo run -p lock-cli -- restore env
cargo run -p lock-cli -- unprotect env
cargo run -p lock-cli -- remove aws
cargo run -p lock-cli -- remove --restore env
cargo run -p lock-cli -- rotate env
cargo run -p lock-cli -- rotate --all
cargo run -p lock-cli -- verify env
cargo run -p lock-cli -- verify --all
cargo run -p lock-cli -- inspect
cargo run -p lock-cli -- inspect env
cargo run -p lock-cli -- repair
cargo run -p lock-cli -- doctor
cargo run -p lock-cli -- doctor --json
cargo run -p lock-cli -- tui
cargo run -p lock-cli -- key export ~/lock-age-identity-backup.txt
cargo run -p lock-cli -- list
cargo run -p lock-cli -- cat aws
cargo run -p lock-cli -- edit env
cargo run -p lock-cli -- run --env DOTENV=env -- node app.js
cargo run -p lock-cli -- run --file env:.env -- npm test
cargo run -p lock-cli -- run --daemon --env DOTENV=env -- node app.js
cargo run -p lock-cli -- run --mapped -- npm test
cargo run -p lock-cli -- exec -- npm test
cargo run -p lock-cli -- pipe env -- sh -c 'cat >/tmp/secret-copy'
cargo run -p lock-cli -- daemon ping
cargo run -p lock-cli -- daemon list
cargo run -p lock-cli -- daemon cat aws
cargo run -p lock-cli -- audit
cargo run -p lock-cli -- status
cargo run -p lock-cli -- completion bash > lock.bash
cargo run -p lock-cli -- man > lock.1Installed binary names:
lock
lockdcargo run -p lockd -- --cache-ttl-seconds 300The daemon listens on the configured Unix socket, currently $XDG_RUNTIME_DIR/lockd.sock or /tmp/lockd.sock.
The MVP supports JSON lines:
{"type":"ping"}
{"type":"list"}
{"type":"decrypt","reference":"aws"}The daemon caches decrypted secret bytes in memory for the configured TTL. lock run --daemon ... uses the daemon, so a sudo-gated decrypt can be reused during the TTL instead of prompting repeatedly.
Future daemon work:
- per-process authorization
- request origin inspection
- approval TTLs per command
- OpenTUI pending-request screen
- command-based policy for
lock execandlock run
The TypeScript manager uses @opentui/core. It is intentionally outside the security-critical path and shells out to the Rust lock CLI.
Preferred launch path:
cargo build
cargo run -p lock-cli -- tuilock tui runs sudo -v before opening the UI, then launches the OpenTUI app with LOCK_BIN set to the current lock binary.
Direct dev launch:
cargo build
cd ui
npm install
LOCK_BIN=../target/debug/lock npm run devCurrent manager actions are performed inside the OpenTUI interface with built-in inputs, modals, a viewer, and a scrollable multiline editor:
- list secrets
- inspect encrypted/plaintext state
- protect file in place
- edit selected secret in a TUI textarea, then save through
lock write - complete path fields with
Tab, relative to the directory wherelock tuiwas opened - run a command through
lock exec - remove selected secret
- unprotect selected secret
- repair plaintext inline files
- view audit log
- export the root-owned age identity backup with
c
- age-compatible encrypted blobs at rest
- public recipient stored in user config
- private identity stored root-owned at
/etc/lock/identity.txt - decryption gated through
sudo age -d -i /etc/lock/identity.txt ... - secret metadata under the user data dir:
~/.local/share/lock/... - audit log under user state dir:
~/.local/state/lock/audit.log lock protectauto-initializes lock if config is missing, encrypts a file in place, records its path mapping, and leaves age ciphertext at the original pathlock restoredecrypts a secret back to its mapped path or an explicit target path while keeping it encrypted in locklock unprotectrestores plaintext and removes the encrypted secret from locklock removedeletes encrypted secret metadata/blob;lock remove --restorerestores plaintext firstlock rotatedecrypts and re-encrypts secrets with the current configured recipientlock verifychecks that encrypted secrets can be decrypted without printing plaintextlock inspectreports whether managed files are encrypted, plaintext, or missinglock repairre-encrypts plaintext inline files after interrupted/manual restoreslock pipefeeds a secret to a command's stdin without printing it directly fromlocklock doctorchecks dependencies, config, store, and secret countslock tuiasks for sudo upfront and opens the OpenTUI managerlock key export <path>exports the root-owned age identity so you can back it up safelylock run --filematerializes plaintext into a temporary private file and removes the symlink after the command exitslock run --mappeddecrypts mapped inline files for the child command lifetime, then re-encrypts them on exit or Ctrl-C/SIGTERMlock execis shorthand forlock run --mapped --lock completion <shell>emits shell completionslock man [path]emits a man page to stdout or a file
printf 'API_TOKEN=secret' > .env
lock protect env .env
# Normal userland process only sees age ciphertext:
node -e "require('fs').readFileSync('.env', 'utf8')" # returns ciphertext, app parsing fails
# Explicitly authorized launch gets plaintext .env only for the child lifetime:
lock exec -- node app.jsWe intentionally do not leave protected plaintext files in place. If .env remains readable as plaintext after protection, compromised package scripts can still steal it. The default lifecycle is:
lock protect env .env
.env is replaced with an age encrypted blob
lock metadata records that env maps to .env
lock exec -- command
sudo-gated decrypt
.env is plaintext for the child command
.env is re-encrypted when the command exits or lock receives Ctrl-C/SIGTERM
lock unprotect env
.env is restored as plaintext permanently
lock metadata is removedlock is a usable MVP: Rust core, CLI, daemon, OpenTUI manager, and black-box integration tests are in place. The main deferred areas are packaging, shell completions/man pages, richer daemon policy, and clean-machine smoke testing with real sudo.