What Can Go Wrong
Assets, Attackers, and Trust Boundaries
Every test you wrote in
To ask that in an orderly way you need three
What is worth taking or changing
Everything the reading list knows is in state. It has four parts that matter here, and an attacker wants something different from each. This block uses a small copy of the state:
The books are the reason the service exists, and the harm there is a change: a book edited or deleted by someone who should not be able to. The other three are different. Here the harm starts with a read, because each of them can be used as soon as someone has it. A password logs in. A token writes. A session id is a login that already happened. Changing one would be harm too, but no route on this server changes a password or a token, so reading is where an attacker begins.
Things a service holds that are worth protecting are called its assets. There is a fifth one that is not in state at all: the service being there when its users need it. Chapter 6 returns to that one.
Who is asking
An attacker is easier to reason about when you define them by what they can reach, not by how clever they are. Three are enough for this course.
A stranger on the network has no account. They can send any bytes to any route, as often as they like, and they see the responses. If the service runs on plain HTTP and the stranger sits somewhere on the path between a user and the server, they may also read what that user sends. Networks with Python showed that observer on the path, and
A logged-in reader has an account. Think of ben: he has a valid session and a valid token, and everything the routes offer him is his to use. He becomes an attacker when he wants something that is ana’s.
Someone with a copy of the data never talks to the server. They have a backup file, a dump of the state, or a laptop that was left on a train. They read state directly.
Someone finds an old backup file of the reading list's data. Which attacker are they?
Where data crosses
Now look at handle(request, state) with those attackers in mind. Its two
Everything in request was written by a client: the state is the server’s own. The line between the two is a trust boundary: a place where data moves from somewhere you do not control into somewhere you do.
outside | inside
|
client --- request ----------> | handle(request, state) <--> state
<-- response ---------- |
|
the trust boundary
The client and the network are on the outside, and state is on the inside. A request crosses the boundary inward. The adapter turns its bytes into the request read_body() reads the body under its size limit, and handle() decides everything else.
Here is the part of handle() that serves one book, shortened:
elif len(segments) == 2 and segments[0] == "books":
book_id = book_id_from(segments[1])
refusal = None
if method == "PUT" or method == "DELETE":
refusal = write_error(request, state)
...
elif method == "PUT":
response = book_request_error(request)
if response is None:
response = replace_book(request, state, book_id)
Which of these names hold outside data? segments does, because it was cut from the request target. So does book_id, which was made from a segment. write_error reads request["headers"], and replace_book reads the state["books"] is the only inside data in sight. Your server already treats the outside data with care here. It checks that the token is known, that the segment is a number, and that the body is a valid book, all before it changes anything. That habit has a name now: outside data is checked at the boundary before it is trusted.
In handle(), which value has crossed the trust boundary?
The second crossing
Data also crosses the boundary in the other direction. A response carries inside data out to a client. So does a line in a log, and so does a backup. Each one is a copy of something the server knew, now sitting in a place the server does not control. Lesson 4 looks at what a backup of this service gives away.
That is where the third attacker lives. Every check in handle() runs when a request arrives. The person with a copy of the data sends no request, so none of those checks ever runs for them.
Why can no check inside handle() stop the attacker who has a copy of the data?
A threat model
You now have three short lists: the assets, the attackers with what each can reach, and the boundary with its two crossings. Together they are a threat model of the reading list. This one is small and plain on purpose. Its job is to be written down, so that a weakness can be stated against it: this attacker, across this boundary, reaches this asset.
The next lesson does exactly that, and turns a vague worry into something you can fix.