Why this matters
Every time you open a web page, your browser sends requests to a server, and the server sends back content. Some of that content is stored in your browser for later. Some of it lets a company recognise your browser on other sites weeks later. Most people never see this happening. The settings that control it are spread across menus, and the policies that describe it are long.
This lesson covers three things. The first is the privacy settings in a browser and what each one really does. The second is the cookie, the small piece of data that makes both logins and tracking possible. The third is a method for reading a privacy policy in about ten minutes, so you know what the company does with your data without reading every word.
The lesson uses a few short Python scripts. They use only the standard library, which is the set of tools that ships with Python, so there is nothing to install. The scripts read sample text that is stored in the file, so you can run them offline and get the same output every time.
Passwords, two-factor login and phishing are covered in earlier lessons in this module. This lesson does not repeat them.
The idea: a cookie is a note the site leaves in your browser
A cookie is a small piece of text that a website asks your browser to store. Each cookie is a name and a value, such as session=abc123. When your browser sends a request to that website later, it includes the cookies that belong to it. The server reads the note and recognises the browser.
Here is an everyday example. You log in to a shop site. The server replies with a cookie named session. For the rest of your visit, the browser sends that cookie with each request, so the shop knows the requests come from you and keeps you logged in. Without the cookie, every page would ask for your password again.
The same mechanism also serves tracking. Suppose a news site shows an advertisement that loads its code from a company called an ad network. The code runs in your browser and asks for a cookie with a random ID. Then, when you visit a different news site that also shows that network's ads, the browser sends the same ID back. The network can now connect the two visits, and it can do this for thousands of sites.
Two terms describe where a cookie comes from:
- A first-party cookie is set by the site whose address is in your address bar. The shop setting its own
sessioncookie is an example. - A third-party cookie is set by a different site that is loaded inside the page, such as an ad or a video embed. The ad network's ID cookie is an example.
A cookie is not a program and cannot run code. It is data. The browser decides what to send based on rules in the cookie, and those rules are what the next worked examples read.
Browser privacy settings
Browsers have settings for cookies and tracking. Menu names change between browsers and versions, so this section describes what each kind of setting does, not where to click.
Blocking third-party cookies stops a page from storing cookies that belong to a site other than the one you are visiting. Ads and embedded widgets are the usual cases. Your own logins usually keep working, because they are first-party cookies. The side effect is that some embedded things stop working, such as a payment window or a login button that runs on another site.
Clearing cookies when the browser closes removes cookies when you quit. You will be logged out of most sites each time, which is annoying, but no long-lived ID survives between sessions. You can allow specific sites to keep their cookies, so that your bank still remembers you.
A private window stops the browser from saving history, cookies and form entries after you close the window. It does not hide you from the websites you visit, from your internet provider, from the owner of the Wi-Fi network you use, or from your employer's network if you are on it. It limits what is stored on your own computer and nothing more.
Do Not Track is a setting that sends a request header saying you do not want to be tracked. It is a request, not a rule. Most sites do not act on it, and some never did. Global Privacy Control is a newer signal of the same kind. Some privacy laws in some regions require sites to honour it, so whether it has effect depends on where you live and on the site. Neither one blocks anything in the browser itself.
Content blockers are extensions that stop the browser from loading known tracker addresses at all. They are the most direct control, but they can break pages that depend on the blocked code.
Blocking all cookies is the most drastic setting. It stops tracking, and it also stops logins, shopping carts and any site that needs to remember a choice. Most people get better results by blocking third-party cookies and by clearing cookies on exit for sites they do not use often.
Reading a cookie's settings
When a server sends a cookie, it sends a header called Set-Cookie. The header holds the name and value, followed by settings separated by semicolons. Each setting changes what the browser does with the cookie. These are the settings that matter most:
- Domain names the site or sites that receive the cookie back. If it is missing, the cookie goes only to the exact site that set it. If it is
.tracker.test, the cookie goes to that domain and every site under it, which is how one tracker reaches many sites. - Path limits the cookie to a part of the site, such as
/, which means the whole site. - Max-Age is the number of seconds the cookie lasts. A cookie with no Max-Age or Expires setting is a session cookie, which the browser usually deletes when it closes.
- Secure means the cookie is sent only over an encrypted connection (https).
- HttpOnly means page code running in the browser cannot read the cookie. It is a protection against some attacks, and it does not stop the browser from sending the cookie to the server.
- SameSite controls whether the cookie is sent when the request starts from a different site.
LaxandStrictlimit this.Noneallows it, and browsers rejectSameSite=NoneunlessSecureis also set.
Python's http.cookies module can read these settings. The first example reads two cookies: one for a login and one that is clearly a tracker.
Save this as read_set_cookie.py:
from http.cookies import SimpleCookie
headers = [
"session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=1209600",
"uid=7f3a9c; Domain=.tracker.test; Path=/; Secure; SameSite=None; Max-Age=31536000",
]
for header in headers:
jar = SimpleCookie()
jar.load(header)
for name, morsel in jar.items():
print("name:", name)
print("value:", morsel.value)
print("domain:", repr(morsel["domain"]))
print("max-age (days):", int(morsel["max-age"]) // 86400)
print("httponly:", bool(morsel["httponly"]))
print("secure:", bool(morsel["secure"]))
print("samesite:", morsel["samesite"])
print()Run it:
python3 read_set_cookie.pyOutput:
name: session
value: abc123
domain: ''
max-age (days): 14
httponly: True
secure: True
samesite: Lax
name: uid
value: 7f3a9c
domain: '.tracker.test'
max-age (days): 365
httponly: False
secure: True
samesite: None
Read the two results side by side. The session cookie has no domain, so it goes only back to the site that set it. It lasts 14 days, which is 1,209,600 seconds divided by 86,400 seconds per day. It is marked HttpOnly, so page code cannot read it. Its purpose is to keep a login, and nothing in its settings lets it reach another site.
The uid cookie has a domain of .tracker.test. The leading dot means that every site under that domain can receive it. It lasts 365 days, and it is not HttpOnly. Its value, 7f3a9c, is an identifier. Nothing in the cookie says what the identifier is for, which is why a cookie with a long life and a wide domain deserves a closer look.
The samesite line prints None as text. The Python value is the string None, which is the SameSite setting the server asked for. It is not Python's None. A setting the header leaves out comes back as an empty string.
Worked example 2: counting third-party hosts on a page
A page is more than its own HTML. It loads scripts, images, stylesheets and embedded frames from other addresses. Each of those addresses has a host, the part of the address that names the server, such as ads.tracker.test. A third-party host is any host that differs from the host of the page you are reading.
The next script reads the HTML of a page and lists the hosts that the page loads resources from. It looks at four kinds of tag: script, img, iframe and link. Save this as third_party_scan.py:
from html.parser import HTMLParser
from urllib.parse import urlsplit
PAGE_URL = "https://news.example.test/world/story-42"
PAGE_HTML = """<!doctype html>
<html>
<head>
<link rel="stylesheet" href="https://news.example.test/static/site.css">
<script src="https://cdn.example.test/js/app.js"></script>
<script src="https://ads.tracker.test/pixel.js"></script>
</head>
<body>
<img src="https://news.example.test/images/photo.jpg" alt="photo">
<img src="https://metrics.analytics.test/collect?page=42" alt="">
<iframe src="https://video.social.test/embed/abc"></iframe>
<a href="https://news.example.test/world">World</a>
<a href="https://other-site.test/">Other site</a>
</body>
</html>
"""
WANTED = {"script": "src", "img": "src", "iframe": "src", "link": "href"}
class ResourceFinder(HTMLParser):
def __init__(self):
super().__init__()
self.urls = []
def handle_starttag(self, tag, attrs):
if tag not in WANTED:
return
for name, value in attrs:
if name == WANTED[tag] and value:
self.urls.append(value)
finder = ResourceFinder()
finder.feed(PAGE_HTML)
page_host = urlsplit(PAGE_URL).hostname
third_party = {}
for url in finder.urls:
host = urlsplit(url).hostname
if host != page_host:
third_party[host] = third_party.get(host, 0) + 1
print("page host:", page_host)
print("resources found:", len(finder.urls))
print("third-party hosts:")
for host, count in sorted(third_party.items()):
print(f" {host}: {count}")Run it:
python3 third_party_scan.pyOutput:
page host: news.example.test
resources found: 6
third-party hosts:
ads.tracker.test: 1
cdn.example.test: 1
metrics.analytics.test: 1
video.social.test: 1The HTMLParser class reads the HTML one tag at a time and calls handle_starttag for each opening tag. The handle_starttag method keeps only the tags in WANTED, and only the attribute that holds a URL for each. urlsplit breaks an address into its parts, and .hostname returns the host. The script then compares each host with the page's host.
Look at the results. news.example.test is the page's own host, so its two images, its stylesheet and the link back to the story are not counted. The link to other-site.test is not counted either, because a plain link does not load anything when the page opens. Only resources the page loads count.
The metrics.analytics.test host is the one to notice. It is an image with an empty alt text and a URL that ends in collect. Image tags are often used as tracking pixels, which are images too small to see, where the request for the image is the tracking. The script cannot tell that from the HTML alone, but it can show that the page sends a request to a host you did not choose.
The script has two limits. The first is that cdn.example.test is counted as third party, because its host name differs from the page's. It may belong to the same company, so a different host does not always mean a different company. The second is that the script reads only the HTML it is given. Scripts often load more scripts while they run, and those requests do not appear in the HTML. A browser's developer tools show the full list of requests, which is the better tool for that job.
Worked example 3: finding the parts of a privacy policy that matter
A privacy policy is the document in which a company describes what it does with your data. Most are long, and most readers stop after the first paragraph. A quick reading needs five answers. Answer these in order:
- What data do they collect?
- Do they share it with other companies, or sell it?
- How long do they keep it?
- Which cookies and trackers do they use?
- How do you ask for a copy of your data, or ask them to delete it?
Those five questions can be found with a few keywords. The script below prints every line of a policy that contains one of the keywords. It is a finder, not a judge. It shows where to read, and you still have to read those lines.
Save this as policy_finder.py. The policy text is a made-up example, written for this lesson:
import re
POLICY = """Example Notes Privacy Policy
1. What we collect
We collect the email address you give us when you sign up.
We also record the pages you open and the device type you use.
2. Cookies
We use cookies to keep you logged in. Our ad partners set their own cookies on our site.
3. Sharing
We may share your data with service providers that run our app.
We sell aggregated usage reports to advertisers.
We never share your notes with anyone without your permission.
4. How long we keep data
Account data is kept while your account is open.
Deleted notes are kept for 90 days in backups.
5. Your choices
You can request a copy of your data by email.
You can delete your account from the settings page.
"""
KEYWORDS = ["collect", "sell", "share", "third", "partner", "keep", "delete", "retain", "cookie"]
pattern = re.compile("|".join(KEYWORDS), re.IGNORECASE)
for number, line in enumerate(POLICY.splitlines(), start=1):
if pattern.search(line):
print(f"line {number}: {line}")Run it:
python3 policy_finder.pyOutput:
line 3: 1. What we collect
line 4: We collect the email address you give us when you sign up.
line 7: 2. Cookies
line 8: We use cookies to keep you logged in. Our ad partners set their own cookies on our site.
line 11: We may share your data with service providers that run our app.
line 12: We sell aggregated usage reports to advertisers.
line 13: We never share your notes with anyone without your permission.
line 15: 4. How long we keep data
line 17: Deleted notes are kept for 90 days in backups.
line 21: You can delete your account from the settings page.The script builds one pattern from the keyword list. The re.IGNORECASE flag makes Sell and sell match the same way. The loop prints each line that has a match, with its line number so you can find it again in the full text.
Now read the output as a reader would. Line 12 says the company sells reports to advertisers. The word "aggregated" usually means the reports combine many users' data. It does not mean that the company stopped selling, so a sale is still a sale, and the line answers question 2. Line 13 is a promise about notes, and it covers only notes, not the email address or the page history. Line 17 answers question 3 for backups, which the policy treats separately from the account. Line 21 answers question 5 for deleting an account.
The finder also picks up unhelpful lines. Line 3 is a heading that contains the word "collect", and line 8 mentions "cookies" twice without saying how long they last. This is the trade-off of a keyword search. It finds the places to look, quickly, but it misses meaning. A line such as "we may disclose information to vendors" would be missed by this list, so the list should grow as you read more policies.
When a policy does not answer a question, that gap is an answer too. This sample says nothing about how long cookies last, so a careful reader would note it and ask the company.
Common mistakes
Treating a private window as anonymity. A private window clears what the browser saves on your computer. The sites you visit still receive your requests, and your network can still see them. Use a private window to keep a shared computer clean, not to hide from a website.
Assuming Do Not Track stops tracking. The setting sends a request that sites may ignore. A site that does not honour it keeps tracking you as before. Use blocking, not the header, when you want tracking to stop.
Blocking every cookie and then wondering why logins fail. The error is a symptom, not a bug in the site. The site cannot store the session cookie it needs. Allow first-party cookies, and block the third-party ones.
Reading a cookie name as proof of its purpose. A name such as uid or _ga does not state what it does. Look at the domain, the lifetime, and whether it is HttpOnly. Those settings are harder to change and say more.
Reading the first lines of a policy and stopping. The sharing and retention sections are usually lower down. The keyword finder in the example points to them, so use it to jump there.
Using a cookie reader on a cookie that is not there. A wrong name stops the script with an error, which Python prints as a traceback, the list of lines that led to the error. Here is the traceback from looking up a cookie called sesion when the header has session. Line numbers and the ~ and ^ marks vary by Python version:
Traceback (most recent call last):
File "<string>", line 4, in <module>
print(jar['sesion'].value)
~~~^^^^^^^^^^
KeyError: 'sesion'The last line names the key that was not found. Check the spelling against the header, and remember that cookie names are case-sensitive.
Practice
Exercise 1. A server sends this header: Set-Cookie: cart=9981; Path=/; Max-Age=3600; Secure; HttpOnly. Does the cookie last until the browser closes? Can page code read it? Is it a third-party cookie?
Show the answer
It does not last until the browser closes. The Max-Age=3600 setting makes it last one hour, so it is not a session cookie. Page code cannot read it, because of HttpOnly. It has no Domain setting, so it goes only back to the site that set it. The header alone does not settle whether it is first-party, because that depends on which site sent it. If the site in the address bar sent it, it is first-party. The header alone cannot show whether the cookie is used for tracking, so you would need to check how the site uses it.
Exercise 2. A page at https://shop.example.test/cart has a script from https://shop.example.test/app.js and an image from https://pixel.ads.test/x.gif. Using the rule in worked example 2, how many third-party hosts does the page have, and which one is it?
Show the answer
One. The script comes from the page's own host, so it is not counted. The image comes from pixel.ads.test, which differs from shop.example.test, so that is the one third-party host. Its name suggests a tracking pixel, which is worth checking.
Exercise 3. Using the sample policy in worked example 3, which line answers the question "Do they sell data?", and what does that line say?
Show the answer Line 12 answers it: "We sell aggregated usage reports to advertisers." The word "aggregated" means the reports combine many users' data, but the line still says the company sells reports. A reader should note that line and ask whether the reports include anything that identifies a person.
Exercise 4. Write Python that reads the header uid=abc; Domain=.tracker.test; Path=/ and prints the domain of the uid cookie.
Show the answer
from http.cookies import SimpleCookie
jar = SimpleCookie()
jar.load("uid=abc; Domain=.tracker.test; Path=/")
print(jar["uid"]["domain"])Output:
.tracker.testThe leading dot is part of the domain setting. It means the cookie goes to that domain and to every site under it.
Exercise 5. Explain in two sentences what blocking third-party cookies does that a private window does not, using the ad network example from this lesson.
Show the answer Blocking third-party cookies stops the ad network's ID cookie from being stored and sent when the ad loads on other news sites, so the network cannot connect those visits through that cookie. A private window only removes what is saved on your own computer when you close it, so the ad network still receives each request and can recognise the visits while the window is open.
Summary
- A cookie is a small note that a site asks your browser to store and send back. First-party cookies come from the site you are on, and third-party cookies come from a different site loaded in the page.
- The
Domain,Max-Age,HttpOnly,SecureandSameSitesettings decide where a cookie goes and how long it lasts. - Blocking third-party cookies and using a content blocker stop most cross-site tracking. A private window only clears what is saved on your computer.
- Do Not Track is a request that sites may ignore.
- A page's HTML shows the hosts it loads resources from, and a host different from the page's own is a third party.
- A privacy policy can be checked for five answers: what is collected, who receives it, how long it is kept, which cookies are used, and how to ask for deletion.