TECHDEV Β· Open-source tool
leaseguard
Your coding agent asks: "can I run npm run deploy?" You glance at the diff, say yes, go back to your coffee. It runs the command 40 minutes later, from a different directory, against code that changed twice since you approved. You approved the command at 10:00. You did not approve the command at 10:40. But nobody wrote that down, so the approval quietly survived past its own relevance.
That is the "stale approval" problem in agentic workflows: a yes with no expiry date, no scope, and no receipt. leaseguard turns your "yes" into a lease: signed, time-boxed, directory-scoped, and logged:
# you (the human) grant a 10-minute lease for the deploy command, in this directory
leaseguard grant "npm run deploy" --for 10m --note "hotfix for #42"
# lease 9f2c1ab4e011 granted
# valid : 10m (until 2026-10-20T12:25:00.000Z)
# scope : /home/you/myapp
# token : eyJ2IjoxLCJpZCI6...
# the agent runs everything through the gate
leaseguard run --lease <token> -- npm run deployThe gate checks, in order: the standing deny policy (deny always wins, even over a valid lease) β the lease signature β expiry β directory scope β command pattern. Then it runs the command, passes through its exit code, and appends the decision to a hash-chained ledger. leaseguard audit shows you what ran, what was denied, which approvals died quietly unused, and whether anyone tampered with the log.
Node 18+ Β· No dependencies Β· MIT
Not a rental-lease app: leaseguard puts time-boxed leases on agent-run command approvals, so a “yes” can’t be quietly replayed 40 minutes later.
Demo: a real run
Real output from the leaseguard README β a 3-second lease, used once, then expired (from examples/demo.sh):
### 1. grant a 3-second lease for the deploy command
lease <id> granted
pattern : echo deploy-*
valid : 3s (until <ts>)
scope : <tmpdir>
note : demo lease
token: <redacted>
run it with: leaseguard run --lease <token> -- echo deploy-<arg>
### 2. run it right away -> allowed
deploy-backend
### 3. run it again after the lease expired -> denied
leaseguard: denied: lease <id> expired at <ts> β ask for a fresh approval
### 4. audit: the stale approval died quietly, nothing ran on it
leaseguard audit
leases : 1 total β 0 active, 0 revoked, 1 expired (used), 0 expired unused
runs : 1 allowed, 1 denied
ledger : 3 entries, hash chain OK
recent denials:
[<ts>] lease-expired: echo deploy-backendOutput copied exactly from the leaseguard README.
The problem it solves
You approved the command at 10:00. You did not approve the command at 10:40 β from a different directory, against code that changed twice since you looked. Every approval a human gives an agent is a yes with no expiry date, no scope, and no receipt. leaseguard writes all three down: each approval becomes a signed lease with a time box, a directory scope, and a hash-chained audit entry.
From the leaseguard README ("The problem").
Install
3 steps. No dependencies. Node 18+.
git clone https://github.com/techdevfun/leaseguard.git
cd leaseguard
npm link # or: chmod +x leaseguard.js && ln -s $PWD/leaseguard.js ~/.local/bin/leaseguardVerify it works:
leaseguard help
npm test # 21 tests, ~7 secondsCommands copied exactly from the leaseguard README ("Install").
Usage
Grant a lease β the pattern is a glob matched against the full command string:
leaseguard grant "npm run deploy" --for 10m # default scope: current dir
leaseguard grant "kubectl rollout restart *" --for 2m --scope ./infra --note "incident #7"
leaseguard grant "rm -rf ./dist" --for 30sDurations: 30s, 10m, 2h, 1d (default 15m).
Run through the gate β the agent's wrapper. Exit codes: 0 ran (the command's own code passes through), 2 denied (no lease / no policy allow), 3 bad or expired lease, 4 policy deny:
leaseguard run --lease $TOKEN -- npm run deploy
leaseguard run --lease-file ./lease.token -- npm run deploy # avoid the token in `ps`
LEASEGUARD_LEASE=$TOKEN leaseguard run -- npm run deploy # or via env varCommands run directly, no shell β the exact approved argv is what executes. Need pipes? Run sh -c explicitly and make the pattern match the full sh -c "..." string.
Standing policy β always-on allow/deny globs, no lease needed for the boring stuff. Deny always wins:
{ "allow": ["npm test", "git status"], "deny": ["rm -rf *", "curl * | sh", "git push * --force"] }leaseguard run --policy examples/policy.json -- npm testManage & audit:
leaseguard leases # active / expired / revoked
leaseguard revoke 9f2c1a # kill a lease now (prefix match ok; an ambiguous prefix is refused, not guessed)
leaseguard audit # summary + tamper check of the ledger
leaseguard audit --json # machine-readableEverything lives in ~/.leaseguard/ (key, leases.json, ledger.jsonl); point it elsewhere with LEASEGUARD_HOME.
Commands and exit codes copied exactly from the leaseguard README ("Usage").
Honest limits
- It does not stop a determined adversary with your file permissions. Anyone who can read
~/.leaseguard/keycan mint leases. Run the grant side from your shell or your CI β not from the agent. - The token on a command line is visible in
ps; use--lease-fileor the env var for real use. - It checks what was approved and when β it does not judge whether the command itself is wise. A lease for
rm -rf *is still your fault.
Limits copied exactly from the leaseguard README ("What this does NOT do").
Roadmap β not shipped yet
--approve-onceleases (single use, then auto-revoke)leaseguard watchβ tail the ledger live during an agent session- Optional agent-side hook examples (Claude Code / Codex pre-tool hooks)
Roadmap copied from the leaseguard README; none of these have shipped yet (2026-10-30).
License
MIT β see LICENSE in the repository.
Related tools
More open-source tools from TECHDEV: sessguard, runverdict, gheist-guard, vetgate, context-diet, ci-failure-detective, agent-seatbelt, devsecops-os, env-doctor, skill-audit, logtrim, testguard, breakcheck, keymap, memgraph, ggufit, factfence, jsonfold
Have a say
Vote for this tool, suggest a feature, or discuss it with other operators on the community board.