HTTPS, Certificates, and Security Headers
Headers That Instruct a Browser
The connection is in order: the client knows who holds the other end. Now look at what arrives over it. The page of your service holds text that came from requests, such as book titles. In Chapter 7 you put one html.escape call between a title and the page. If one
One line of defense is not much for a mistake that is so easy to make. A second line has to live in the browser, because the browser is where the script would run. The server can ask for it with a
A policy is a list of sources
The header is Content-Security-Policy. Its value is a policy: a
content-security-policy:
default-src 'self'; form-action 'self'; frame-ancestors 'none'
Split it the way a browser does. Run this:
The first line is default-src allows ["'self'"]. default-src is the directive for everything a page loads: scripts, images, style sheets. 'self', with its quotes, means the page’s own default-src 'self' says that this page may load things from its own origin and from nowhere else.
A policy changes nothing on the server and nothing on the connection. The server sends the page as it is. The header is an instruction, and only a browser carries it out.
wire.py fetches the page and its policy header. What does the policy change?
What default-src ‘self’ stops
Suppose the escaping is missed once. These are the two versions of a title inside its <span class="title"> in the page, escaped and not:
<script src="https://other.example/steal.js"></script>
<script src="https://other.example/steal.js"></script>
The second line is a script element. Under default-src 'self', a browser that follows the standard refuses to load it, because https://other.example is not the page’s origin.
An attacker does not need another site, though. The script can sit in the page itself, between the tags, or in an attribute such as onclick. Those are inline scripts. An inline script is loaded from no file, so 'self' does not cover it. A policy allows inline scripts only when it says so, with the source 'unsafe-inline'. The name is a warning. Your policy does not contain it, so a browser runs no inline script and no inline event handler on your page.
Here is a model of that decision. It covers two rules of a long standard: where a script file comes from, and whether a script is inline. Run it:
The first two lines are https://reading.example/app.js True and https://other.example/steal.js False. This policy works for your page because page.html has no script and no styles of its own. A larger page that needs inline scripts lists each one in its policy with a nonce or a hash, and a new policy can be tried in report-only mode first. Those are names to recognize.
A missed html.escape lets <script>alert(1)</script> into the page, with the code between the tags. The response says default-src 'self'. What does a browser do with it?
What does the last line of the second block print?
What it does not cover
default-src is a fallback for the directives about loading. Two of the things a page can do are not loading, and they do not fall back to it.
The first is where a form on the page may send its fields. Injected markup could change the form’s target to another site. form-action 'self' keeps every form of the page pointed at its own origin.
The second is who may show the page inside a frame. Another site can put your page in an invisible frame and trick a reader into clicking on it, which is called clickjacking. frame-ancestors 'none' tells the browser that no page may frame this one. An older header, X-Frame-Options, asks for the same.
Which of these does default-src 'self' leave open?
A second lock, not a replacement
The policy does not make escaping optional. Without html.escape, a title can still add markup: a heading, a link, a form that asks for a password. No script runs, and the page still shows what an attacker wrote. Escaping keeps the page correct. The policy limits the damage when escaping is missed once. And the policy only helps in a browser that applies it.
With this policy in place, is html.escape in render_page() still needed?
You can read the policy now. No response of your service carries it yet. The next lesson adds it, with three more headers, in one