← All articles

Governance and refusal

Deny by default is a claim you can test in ten seconds

Almost every platform claims it. Very few survive the obvious experiment. Here is the experiment.

Manah Khalil/

"Deny by default" appears in more or less every security datasheet. It is uncontroversial, everybody agrees with it, and it is almost never tested, because testing it feels like testing that the sun rises.

So test it. What follows is the whole procedure. It takes an afternoon at the outside, most of which is finding somebody with permission to delete a configuration.

The experiment

1 — Choose a non-critical application. One you can break for ten minutes without a conversation.

2 — Remove its configuration entirely. Not disable. Remove. You are testing what happens in the absence of a decision, so there must be no decision.

3 — Make an ordinary request from it. Not a hostile one, not a malformed one. The dull request it makes all day.

4 — Repeat on every surface you govern. Model traffic, tool traffic, outbound web. A platform can be correct on one and permissive on another, and the one you did not test is the interesting one.

5 — Read what came back. You are looking for three things in each response: a refusal, a reason naming which rule refused, and the same result on the very first call as on the tenth.

Anything that succeeds is a finding. A refusal that cannot tell you why is also a finding, because you cannot distinguish "denied by default" from "broken", and you will spend the afternoon finding out which.

Why platforms fail it

Not through carelessness. Through a small number of very specific ordering problems.

The first request outruns the policy. A caller arrives that the system has not seen. Something has to be created to represent it. That creation is often written to happen in the background, sensibly, so the request is not slowed. And so the request that triggered the creation is decided against the state that existed before it. The very first call from a brand-new caller — the one nobody has reviewed — gets whatever the general default was. Every subsequent call is refused correctly, which makes it look like a flake rather than a hole.

Creating the record is treated as granting access. A new caller gets an entry so an operator can find it, and the entry is populated with sensible defaults so it is not empty. Now merely appearing has produced permission. Nobody decided that; it is what "sensible defaults" means when the record and the permission are the same object.

The list of things to switch off is written by hand. A new caller should start with nothing enabled, so there is code that turns everything off. Then a capability is added later and not added to that list. It inherits, because inheriting is the behaviour for anything unspecified, and it inherits on. This is invisible until somebody runs the experiment, and it is always the newest capability, which is usually the one that matters most.

There is a fourth failure, where arriving at a destination is enough to have that destination classified, and a caller can therefore change how its own later requests are judged simply by making them. That is a larger family of defect and it has its own piece.

The properties worth insisting on

Nomination is not authorisation. A caller that turns up should be recorded, so a human can see it and act. Recorded as denied, always, regardless of what the general default happens to be. Otherwise a permissive default turns every appearance into a grant, and the answer to "who approved this?" is "it asked".

The call that creates a caller is governed by that caller. Whatever gets created has to apply to the request that caused it, not to the one after. This costs a little on first sight of a new caller, once ever, and it is the difference between the claim being true and nearly true.

Absence means no, and it means no for capabilities added next year. Anything unspecified must refuse, so that a new capability is safe on the day it ships rather than on the day somebody remembers to add it to a list.

These are the properties Squidder is built to hold, which is why the experiment above is a fair one to run against it.

Run it on a Tuesday

The reason to do this on a date of your choosing is that the alternative is an assessor doing it on a date of theirs.

It is a ten second test. The reason to run it is that nobody ever has.