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 deploy

The 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-backend

Output 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/leaseguard

Verify it works:

leaseguard help
npm test   # 21 tests, ~7 seconds

Commands 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 30s

Durations: 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 var

Commands 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 test

Manage & 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-readable

Everything 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

Limits copied exactly from the leaseguard README ("What this does NOT do").

Roadmap β€” not shipped yet

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.