Letting a Model Call Your Code Safely
Give It No Route Out, Then Name the Exceptions
Last timeLimiting What It Can Spend
A sandbox running submitted code gets no address, no route and no resolver, because a rule aimed at the public internet closes only one of the ways out.
The job does not need the network
Ten inputs go in. What the program printed comes back. That is the whole
contract, and it is worth reading twice before any rule is written, because the
data path it describes has no network anywhere on it. The inputs are already on
the machine when the program starts. The output is read back from a handle the
runner already holds. Nothing in the job needs a name resolved, a connection
opened or a packet sent.
Which is why the budget fixed earlier in the course reads the way it does. Two
seconds of processor time, ten seconds of wall clock, 256 MB of memory, 64 MB of
writing, 32 processes, one writable scratch folder, everything else read only,
no route to the network, and nothing in the environment worth stealing. The
network line is not hardening bolted on afterwards. It is a statement that the
capability does not exist, which is a much stronger thing to be able to say than
that it exists and is watched.
Two of the ten things a hostile submission tries are purely a network
question: opening a connection out, and reaching the address the cloud uses to
hand out machine credentials. Both vanish if the sandbox has no address, no
route and nothing that will resolve a name for it. Two attacks out of ten,
closed by removing a capability the job never asked for, is the cheapest wall in
the course, and the only lesson where the right answer is to delete the feature.
Why the default has to be refuse
People argue about defaults as though it were a matter of temperament. It is
not. It is a question about which of two lists you are able to finish writing.
- destinations a process could attempt, over one protocol
- addresses in the family, 4,294,967,296 for the older one
- ports reachable on each address, 65,535
A refusal list has to be complete to be worth anything, and a list of
281,470,681,743,360 entries is not going to be complete. An allow list has to be
complete too, and for this job it is complete at zero entries, because the
contract says the program is handed its inputs and returns its output. Every
sentence in this lesson follows from that asymmetry rather than from any
particular attack.
There is a second reason, and it is the one that matters on the day something
goes wrong. A refusal list is a claim about what an attacker will try. An empty
allow list is a claim about what the machine is able to do. The first is an
opinion and the second can be tested, which is the whole subject of the last
lesson in this course.
The lesson stops here
7 more paragraphs to go
You have read the opening. The rest of the argument, the problems that check whether it landed, and the lines worth keeping at the end all come with a plan.
The first lesson of every course in the library reads the whole way through, free, so you can see exactly what the rest of them are.
See the planThe contentsThis is the reading half
Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.
The contents