0%

What Can Go Wrong

Assets, Attackers, and Trust Boundaries

Every test you wrote in with Python asks the same kind of question: does the right request get the right answer? A login with the right password gets a . A write with a good token changes a book. Security asks about all the other requests, and about people who never send one.

To ask that in an orderly way you need three : what the service holds that someone would want, who that someone might be, and where their data gets in.

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:

Try it

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 as the answer. Chapter 10 returns to it.

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 come from two very different places.

Everything in request was written by a client: the , the target, every header, the body. The server did not choose a single byte of it. Everything in 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 body: outside data, both. 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.