remove_group_members(group: "engineering-all")
remove_group_members(group:
)
One command. No credentials, no configuration, and
nothing to wait for. neti start measures the machine you are standing on and
then turns itself on.
Two things stop a call on day one, and neither is a number: a file you were told about on the screen where it was set, and a target outside the directory you pointed the agent at. The size threshold it picks only ever flags — recorded, you get told, the call runs. A number we chose is not a number we will stop your work on.
**/.env* and **/.git/** — proposed only because they are really on this disk.
~/.ssh/id_rsa lives: one file, under every ceiling anybody would write.
.claude/settings.json.
Not "denied". The number, the ceiling it crossed, and what to do about it — handed back as an ordinary tool result.
The agent gets a number back, not a refusal. That is what makes it narrow the target and try again instead of giving up or working around it. Nothing about the gate reaches the prompt.
remove_group_members(group: "engineering-all")Every image on this page is generated from output the test suite pins byte for byte. If the product stops printing this, the build fails until the picture is regenerated.
No threshold to tune. No model in the decision path. Nothing calibrated on a corpus.
A resolver turns the argument into a count. The count is compared against bands you committed to a file. Where a call touches several things, the verdicts join and the most restrictive one wins.
It fits. Nothing is said, so your existing permission rules keep working.
It runs, and the record marks it as notable.
Somebody other than the operator should decide this one.
It does not run, and the reason carries the number.
Two rules make a verdict sound. No resolver may return 0 for something it could not reach, because an unreachable target and an empty one are opposite situations with the same number. And anything capped or estimated reports a lower bound, which can block safely but can never allow. The targets too large to count are the ones that cannot slip through quietly.
Before any traffic, before any ceilings, and without waiting a week to find out.
This is not a measurement of one call. It is a bound on what the agent could do in a single call, which is the number nobody has. On day one it usually reads: this agent holds a credential that can reach 52,400 people and 214 applications, and nothing today would stop it.
It ships in observe mode with every band left blank on purpose.
A ceiling nobody chose is a ceiling nobody will defend the first time it fires. So the numbers arrive a week later, out of what you actually did. It shows its working, including where the statistics disagree with themselves. You edit the numbers and commit them. Nothing it learns ever reaches the decision path.
A seam without a resolver is a place to write "allow", so this list is the real measure of coverage. Adding one is about 80 lines, and it is the contribution that matters most.
| resolver | unit | one call resolves | cost |
|---|---|---|---|
| fs.paths | objects | a path, directory or glob | local walk, capped |
| db.rows | rows | DELETE or UPDATE, via select count(*) | one scan |
| storage.objects | objects | s3://bucket/prefix | paginated, capped |
| github.repos | repositories | an owner, and every repo under it | one request |
| github.files | objects | owner/repo, files on the default branch | one request |
| entra.principals | principals | a group, everyone in it, nested included | one $count |
| entra.apps | apps | a group, applications assigned to it | one $count |
| entra.guests | principals | a group, the external members only | one $count |
| terraform.destroy | resources | a plan, what it would destroy | local read |
Every runtime has one place where a request becomes an execution. neti sits at that place, so a blocked call comes back to the model as a tool result with the number in it, never an exception that kills the run.
The same call through every door reaches the same verdict, the same magnitude and the same sentence, byte for byte. That is a test, not a claim. A verdict that depends on which door a call arrived through is a bug in the product.
The only seam that can see a harness's built-in tools: Read, Edit, Glob, Grep, Write. No proxy can, because they never travel over a protocol. It is also where the gate is most effective, covering 6 of 10 built-ins against 25 of 160 MCP tools.
Whatever command launched the server becomes an argument to neti gate. The client is not modified, not configured, and not aware, which is why this list can be long without any of it being work.
Four of these have a before-tool callback the gate attaches to. The rest wrap the one method that executes and copy name, description and schema across verbatim. An agent must not be able to tell a gated tool from an ungated one by looking at it.
Those two lists are not equally strong, and the scorecard keeps them apart rather than adding them up. An SDK adapter was driven by that comparison. An MCP client above was never run. What is tested is that neti gates a real MCP server, and that Cursor speaks MCP is a fact about Cursor.
neti decides against rules you declared, not against whether this is a good idea. The gaps are numbered so tests and write-ups can cite them, and the numbers get published whether or not they flatter.
rm -rf $X deletes is a
grammar, not a value, and a gate that guesses is worse than no gate.neti suggest asks a model about the rest, with your key or with a
model running on your own machine, and writes a commented-out fragment a person has to
uncomment. Nothing a model says reaches a decision.All of it is written down in SCOPE.md before you install anything, next to the sentences this project does not say.
On one machine there is nobody to ask, so the gate stops the call. That is correct, and a free install will keep doing it forever.
neti cloud is the somewhere that question can go.
The console, on the synthetic tenant. The rows carry their own "synthetic" tag and the demo badge because nothing here was edited.
The narrowest thing it can. A grant that means more than the person thought is the failure that matters here. Each of these is a test in the public repository.
One policy version across every machine, signed and pinned by digest, instead of a file per laptop that drifts. Session budgets that survive a restart. Per-machine record chains anchored centrally, so a deleted local file becomes detectable rather than simply gone.
Your records stay yours. The local file is the
source of truth. A control plane anchors it and is not an authority over it, and
neti verify works with the network unplugged.
That is the whole rule and it decides every case. Enforcement runs on one machine, so blocking is free. Forever, with nothing withheld and nothing time limited. A second person approving a call does not run on one machine, so it is not.
Every item on the second row is a gap SCOPE.md already documents, or work that only exists because more than one person did it. We are not inventing limitations to sell past.
Nothing is blocked until you write a number. The worst case of installing it is one extra hop.
$ pip install "neti[all]" $ neti demo --hereNothing is blocked by either command. The install itself is checked: just
e2e walks this whole page against the published wheel and asserts every number.
Coverage is not a claim anybody can make on your
behalf. A seam without a resolver is a place to write allow, so the honest
measure of this product is how many arguments it can turn into a count.
tests/corpus/ names 401 parameters across 170 real tool
schemas that nothing here can size, and some of them are sets somebody could measure: a
Slack channel's members, the pages in a Notion database, the rows a Linear filter matches.
A resolver is about 80 lines against
RESOLVER_CONTRACT.md,
which is short and is the actual specification of the product. Two rules do most of the
work — never return 0 for something you could not reach, and anything capped
or estimated reports a lower bound, which can block soundly and can never allow.
Open an issue with the tool and the parameter and we will tell you honestly whether it can be sized. Saying no is part of the contract.