LeastGrant

LeastGrant

Let routine work flow.
Catch the weird stuff.

Your agent asks about git status with the same gravity as git push --force, forty times an hour, until you stop reading and turn permissions off entirely. LeastGrant is the layer in between. It knows the difference between your hundredth npm test and the first time anything on this machine has tried to read a private key.

v0.5.0 · Apache-2.0 · zero dependencies · fully local

leastgrant check
$ leastgrant check "npm test"
  ✓ allow  npm test
 
$ leastgrant check "git status"
  ✓ allow  git status
 
$ leastgrant check "git push --force origin main"
  ? ask  git push --force origin main
 
$ leastgrant check "cat ~/.ssh/id_rsa"
  ? ask  cat ~/.ssh/id_rsa
 
$ leastgrant check "echo x >> ~/.leastgrant/ledger.jsonl"
  ✗ deny  echo x >> ~/.leastgrant/ledger.jsonl
 
real output, captured from v0.5.0 at build time

how it decides

Six steps, in this order

The order is the product. Your own rules sit above the floors, because a rule is an answer you already gave. Learning only ever operates at step five — it decides whether to stop asking about things that were already in the automatic band. It never widens that band.

Not a simulation. Each answer below is the output of leastgrant check run against v0.5.0 while this page was built. The step markers are derived from the same JSON the CLI prints.

npm test

1 Integrity floors deny
2 Your deny rules deny
3 Your allow rules allow
4 Ask floors ask
5 Learned promotion allow
6 Otherwise ask

looked, nothing to say answered never reached

✓ allow The hundredth run of the test suite.

  • you have approved this 8 times across 8 days and 8 sessions
  • it stays inside the project

reach workspace · undo easy · scale many

full output
  ✓ allow  npm test

  what it does   runs the "test" script, which by its name tests or checks the code; the script body
                 is not analysed
  blast radius   reach workspace │ undo easy │ scale many

  why
    • you have approved this 8 times across 8 days and 8 sessions
    • it stays inside the project

git status

1 Integrity floors deny
2 Your deny rules deny
3 Your allow rules allow
4 Ask floors ask
5 Learned promotion allow
6 Otherwise ask

looked, nothing to say answered never reached

✓ allow Looking around costs nothing and happens constantly.

  • you have approved this 8 times across 8 days and 8 sessions
  • it stays inside the project

reach workspace · undo trivial · scale single

full output
  ✓ allow  git status

  what it does   reading git state
  blast radius   reach workspace │ undo trivial

  why
    • you have approved this 8 times across 8 days and 8 sessions
    • it stays inside the project

git push --force origin main

1 Integrity floors deny
2 Your deny rules deny
3 Your allow rules allow
4 Ask floors ask
5 Learned promotion allow
6 Otherwise ask

looked, nothing to say answered never reached

? ask Same program, same project, one flag. Not the same action.

  • this affects something other people depend on
  • this cannot be undone
  • LeastGrant never auto-approves this kind of action, however often it happens

reach production · undo irreversible · scale many

full output
  ? ask  git push --force origin main

  what it does   overwrites history on the remote, discarding commits anyone else may have already
                 pulled
  blast radius   reach production │ undo irreversible │ scale many
  touches        origin

  why
    • this affects something other people depend on
    • this cannot be undone
    • LeastGrant never auto-approves this kind of action, however often it happens

  ╰ this always asks. To pre-answer it, run: leastgrant allow "git push origin main --force"
    --force

cat ~/.ssh/id_rsa

1 Integrity floors deny
2 Your deny rules deny
3 Your allow rules allow
4 Ask floors ask
5 Learned promotion allow
6 Otherwise ask

looked, nothing to say answered never reached

? ask No amount of repetition promotes this one.

  • this reads …/you/.ssh/id_rsa, which holds credentials
  • this reaches …/you/.ssh/id_rsa, which is outside the project
  • LeastGrant never auto-approves this kind of action, however often it happens

reach machine · undo trivial · scale single · secrets reads-secrets

full output
  ? ask  cat ~/.ssh/id_rsa

  what it does   reads a credential file
  blast radius   reach machine │ undo trivial │ secrets reads-secrets
  touches        …/.ssh/id_rsa (credentials)

  why
    • this reads …/you/.ssh/id_rsa, which holds credentials
    • this reaches …/you/.ssh/id_rsa, which is outside the project
    • LeastGrant never auto-approves this kind of action, however often it happens

  ╰ this always asks. To pre-answer it, run: leastgrant allow "cat <path:secret>" --force

curl -sSL https://get.example.com/install.sh | sh

1 Integrity floors deny
2 Your deny rules deny
3 Your allow rules allow
4 Ask floors ask
5 Learned promotion allow
6 Otherwise ask

looked, nothing to say answered never reached

? ask The verdict is about what cannot be read, not what was typed.

  • this runs code that was just downloaded, so what it does depends on what the server sent back
  • this cannot be undone
  • LeastGrant could not fully account for what this command does, and it only auto-approves things it understands
  • LeastGrant never auto-approves this kind of action, however often it happens
  • this command runs 2 separate things; the verdict reflects the most far-reaching one

reach machine · undo irreversible · scale single · not fully understood

full output
  ? ask  curl -sSL https://get.example.com/install.sh | sh

  what it does   runs whatever the download returned, as a sh script
  blast radius   reach machine │ undo irreversible

  this command runs 2 things:
    • curl -sSL https://get.example.com/install.sh  net.fetch
    • sh  exec.unknown

  why
    • this runs code that was just downloaded, so what it does depends on what the server sent
      back
    • this cannot be undone
    • LeastGrant could not fully account for what this command does, and it only auto-approves
      things it understands
    • LeastGrant never auto-approves this kind of action, however often it happens
    • this command runs 2 separate things; the verdict reflects the most far-reaching one

  ╰ there is no safe way to pre-approve this one: LeastGrant cannot see what the code does, so
    it asks every time

echo x >> ~/.leastgrant/ledger.jsonl

1 Integrity floors deny
2 Your deny rules deny
3 Your allow rules allow
4 Ask floors ask
5 Learned promotion allow
6 Otherwise ask

looked, nothing to say answered never reached

✗ deny Editing the thing that is watching. The only outright deny.

  • this would modify LeastGrant's own records, which it does not allow — run leastgrant commands directly instead

reach machine · undo easy · scale single

full output
  ✗ deny  echo x >> ~/.leastgrant/ledger.jsonl

  what it does   writing outside the project
  blast radius   reach machine │ undo easy
  touches        …/.leastgrant/ledger.jsonl (outside)

  why
    • this would modify LeastGrant's own records, which it does not allow — run leastgrant
      commands directly instead

  ╰ nothing changes this — LeastGrant will not let an agent edit its own records

the part that is not an allowlist

Normal ≠ Safe

An action can be the most familiar thing on the machine and still be one LeastGrant will not wave through. Reading a credential, sending data off the box, running code nobody has read — those are decided before learning is consulted at all. There is no number of boring, patient, approved repetitions that adds up to permission to read a private key, because that is not decided at step five.

Safe ≠ Normal

The reverse mistake is worse for you day to day. Something unfamiliar is not therefore dangerous, so LeastGrant does not block it — it asks. New work should cost you one keystroke, not a support ticket. The only outright deny in the default configuration is an agent editing LeastGrant's own records.

Typicality is strong evidence of abnormality and weak evidence of safety. LeastGrant uses it in the first direction only — because a learning permission system that conflates the two can be trained by the thing it is supposed to be watching.

setup

It reads the history you already have

leastgrant init does not ask you to write a policy. It finds the session transcripts your agents have already left on disk, replays every tool call through the decision engine, and shows you what it would have done — including what it would have got wrong.

evidence is typed
4 kindsApproving something and merely doing it are recorded differently. Work that ran unattended teaches LeastGrant what is typical here. It does not count as your consent.
promotion bar
5 → 11Approvals needed to stop asking, by blast radius, spread over at least two sessions and two days so one runaway session cannot bootstrap its own trust. Reads and inspections that stay inside the project take a weaker second route — 8 sightings across 2 sessions, no second day. Nothing that writes, deletes or reaches the network is eligible for it.
denials
permanentApprovals decay on a 90-day half-life. Refusals do not expire, so waiting one out is not a strategy.

What that did on one machine

On the machine LeastGrant was written on, init found 97 sessions across 21 projects — 13,439 real tool calls. Approving the starter bundles it proposed took that history from 5% to 39% of actions running without a prompt, with none of the 58 actions actually refused on record slipping through.

read this before believing the number

That is one developer, one month, one command mix — a sample of one, and the project says so itself. It is on this page because a permission tool that never says how often it gets out of the way is hiding the only number that matters. Re-derive it on your own machine: leastgrant init --dry-run writes nothing and tells you what it found.

The first day looks different

Before it has seen anything, almost everything asks. That is the honest starting state, and it is what the same npm test looks like on a fresh install:

  ? ask  npm test

  what it does   runs the "test" script, which by its name tests or checks the code; the script body
                 is not analysed
  blast radius   reach workspace │ undo easy │ scale many

  why
    • LeastGrant has not seen enough of this yet to stop asking
    • LeastGrant has barely seen this project yet, so it is asking about most things

  ╰ approve this 5 more times (on 2 separate days) and it stops asking — or run: leastgrant
    allow "npm test"

install

Two commands

npm install -g leastgrant
  1. Install it

    Node >=20.10.0. No postinstall script, no account, no configuration file to write.

  2. Let it read what already happened

    leastgrant init replays your existing agent history, reports its own mistakes against it, and proposes a starting set of grants. You answer once.

    leastgrant init
  3. Try it before you trust it

    Ask what it would decide, without running anything. Every verdict on this page came out of this command.

    leastgrant check "git push --force origin main"

Prefer to watch first? leastgrant init defaults to the assist posture, and observe never intervenes at all — run it for a week and it cannot get in your way. leastgrant simulate replays your history against the three postures that reach a verdict, so you can compare before switching; observe is not in that comparison, because a setting that never intervenes has nothing to trade off.

where it runs

One engine, one set of floors

Every adapter calls the same decision path, so the answer does not change with the editor you opened. The status column is deliberately blunt about which integrations have actually been run against a live install and which have only been tested against a published contract.

Claude Code

Partial

LIVE VERIFIED

Codex CLI

Veto only

LIVE VERIFIED

Cursor

Partial

LIVE VERIFIED

GitHub Copilot CLI

Partial

LIVE VERIFIED

Google Antigravity

Partial

LIVE VERIFIED

One caveat that does not fit in a table. Cursor's beforeReadFile event takes allow or deny and has no "ask", so an unfamiliar read is allowed there rather than blocked — turning every unrecognised file read into a hard failure would make the integration unusable. A read of something LeastGrant recognises as a credential is still blocked, and shell commands and MCP calls get the full allow/ask/deny. The full matrix spells this out.

what it is not

It is not a sandbox

It answers a question the agent asks it. It does not confine a process, intercept syscalls, or contain anything already running. And if the hook itself crashes or times out, what happens is the host's call, not ours: Claude Code and Codex CLI run the tool call anyway; Cursor, GitHub Copilot CLI and Google Antigravity block it. LeastGrant is a reliable veto and a best-effort grant on all of them, and it is designed around that asymmetry.

bypass corpus
95Real allowlist evasions — separators, substitution, wrappers, encodings, .. stepped off the end of a symlink — each checked against a LeastGrant deliberately trained with hundreds of approvals for the innocuous-looking prefix. One auto-approval fails the build.
runtime dependencies
0No third-party code in the permission path. The only devDependencies are TypeScript and its types.
shell parser
7,897of 7,900 real agent commands accounted for, 0 crashes, 0.03 ms average — again, one machine's command mix.
fully understood
42.7%Of that same one machine's commands. The rest contain inline code, a script file or a program it has no knowledge of; those are marked not-understood and always ask. It is the largest single source of prompts and the honest answer.

The complete version — what it defends against, what it does not, the adversarial model for the learning itself, and what the v0.1.0 audit left standing — is in the threat model. The security page is the short version, including how to report something.