
'No tools listed' should mean deny, not allow-all
Here's a config bug that has shipped in more systems than anyone would like to admit:
Spots 

Here's a config bug that has shipped in more systems than anyone would like to admit:

In a surprising number of frameworks, an empty or missing list doesn't mean "no access." It means all access. Empty CORS origins → reflect any origin. A Kubernetes pod with no NetworkPolicy selecting it → all traffic allowed. A firewall with the rules commented out → wide open. The pattern is everywhere, and it has a name worth calling out: privilege by omission — the widest possible access, granted not by a decision but by the absence of one. Why omission is the dangerous default
The danger isn't that someone types "allow everything." Someone typing that has made a choice you can see in code review. The danger is that nobody typed anything, and the system filled the silence with maximum privilege. That happens constantly, and invisibly: A refactor renames a field. The old key is now ignored, silently falling back to the default — which is "allow all." A merge drops a line from a list. The list is now empty — which is "allow all." Someone copies a config skeleton, means to fill in the allowlist later, and forgets. The skeleton is "allow all."
A typo in the key name (allowedTools vs allowed_tools) means the field you carefully wrote is never read. What is read is the default: "allow all."
Every one of these looks fine in review. The config reads as intentional. There's no red diff line saying "grant everything" — there's just... less config than there should be. And less config quietly resolved to more power.
Contrast that with the same mistakes under a deny-by-default rule: the refactor, the dropped line, the forgotten allowlist all resolve to less access. The agent or service suddenly can't do its job, someone notices in about five minutes, and they fix it. A too-small grant is a bug report. A too-large grant is an incident you find out about later, from someone else. The rule: name your capabilities, or be rejected The fix is two rules applied together:
The widest access must be an explicit marker you typed. "Everything" is a real, legitimate choice — but it should look like a choice: a literal ["*"], a mode: all, something a reviewer can see and question. Never something you back into by leaving a field blank.
Naming no capabilities at all is an error — reject it, don't default it. This is the part people skip. It's tempting to say "empty list = deny all, safe default, done." But an empty allowlist is almost never what someone meant; it's what they got when something went wrong. Silently denying everything hides the mistake (now you're debugging why the agent won't work). Silently allowing everything is a hole. Rejecting it at load surfaces the mistake immediately, while the person who made it is still looking at the file.
Call it no privilege by omission: fail closed, and fail loud. The ambiguous state — "a grant that names nothing" — shouldn't resolve to a guess in either direction. It shouldn't be representable as a running config at all.
I build Agenthof, an open-source (Apache-2.0, Go) governance layer for AI agents, and we made this a rule the whole system is held to. From its constitution:
Here's a config bug that has shipped in more systems than anyone would like to admit:
