Last Day
Last class we wired up the backend, and your Twitter clone finally came together: Python routing the requests, SQL storing the data, HTML and CSS and Jinja2 rendering the pages, all cooperating in one program. That project is the whole course in one place. It uses every language we learned, and it is the closest thing to a real web application you have built.
Today is the last class, so there is no new tool to learn. Instead we do two things. First we close one security hole that matters for the project you are finishing. Then we step back and look at what you built, what “real” computer science looks like from here, and where you might go next.
You have also earned a joke that would have meant nothing to you in week one:
The webpage is handing out cookies. In week one that was just a cute drawing. Now you know a cookie is the small file a website stores in your browser to remember you between visits, and “grab a byte” and “home sweet homepage” land too. You are on the inside of the web now.
One last bug
Before you demo that Twitter clone, there is one bug you have to hunt down in your own code. It is the single most common way real web applications get broken into, and it has a name: SQL injection.
Here is the setup. Somewhere in your app you take a username and password from the login form and check them against the database. The tempting way to build that query is to paste the user’s input straight into a string:
# username and password came straight from the login form
cursor.execute(
"SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'"
)We glue the user’s username and password into a SQL string and run it. For a normal login this works fine. The problem is that the user controls what goes into that string, and they do not have to send you a normal username.
Suppose someone types bob'-- as their username. Our string becomes:
SELECT * FROM users WHERE username = 'bob'--' AND password = '...'The -- starts a SQL comment, so the database throws away everything after it, including the whole password check. What is left is SELECT * FROM users WHERE username = 'bob'. That returns Bob’s row and logs the attacker in as Bob, no password required. That is exactly the challenge in the optional exercise below: log in as another user without ever knowing their password.
The nastier version goes further than a login bypass: it destroys your data. This comic is the most famous bug report in computing:
A mother names her son Robert'); DROP TABLE Students;--. When the school’s software pastes that name into a query the same way we just did, the '); ends the intended statement and DROP TABLE Students runs as a second command, deleting the student records. The school lost the year’s data because it trusted its input.
These are not only cautionary tales. There is a company in the UK whose registered legal name is a SQL injection string, chosen so that careless systems might break themselves just by storing it. Loading an official government record can be an attack when the code was written without care.
The fix is one line, and you already saw it in the SQL topic: never build queries by gluing in strings. Use sqlite’s binding syntax instead, the ? placeholder:
cursor.execute(
"SELECT * FROM users WHERE username = ? AND password = ?",
(username, password),
)Read the difference. Each ? marks a spot for a value, and we pass the actual values in a separate tuple. Now the database treats username and password as data and never as SQL. If someone sends bob'--, it looks for a user literally named with those six characters, finds none, and denies the login. The quotes and comment characters lose all their power because they never touch the query’s structure. Go through every execute call in your project and make sure not one of them concatenates user input. This is the difference between a demo and a disaster.
Offense and defense
Notice what just happened. To defend the login, we first had to think like an attacker. Strong defense requires knowing how an attacker breaks things.
You practiced both sides this term without always noticing. The password-cracking lab put you on offense, guessing weak passwords by brute force, and the lesson landed from the attacker’s seat: use long passwords, and never reuse one across accounts that matter. Today’s SQL injection is the same coin flipped to defense. A blunt heuristic follows from this: if someone talks confidently about computer security but cannot write code, be skeptical. You cannot defend a system you do not understand well enough to attack.
Breaking things on purpose is also a real, legal, and well-paid job. Big companies run bug bounty programs that invite anyone in the world to hack them: find a genuine vulnerability, report it privately, and get paid, often thousands of dollars, while the company fixes the hole. Stripe, which processes billions of dollars in payments, pays out for serious bugs, and platforms like HackerOne have routed hundreds of millions of dollars to these ethical hackers over the years. Breaking systems for the people who own them, with permission and a paycheck, is a career.
If that sounds fun, here is an optional send-off, worth a little extra credit and nothing at all if you skip it. Stripe used to run Capture the Flag competitions to teach exactly these skills: a ladder of websites, each with a planted vulnerability, where breaking one level opens the next. Level 3 of their 2012 CTF is a small Flask app with the SQL injection hole we just walked through. Download it, log in as the bob user without his password, and come explain how your exploit works. (It is old code that expects Python 2, so you will fight the setup a little; that is part of the exercise.) It is genuinely hard and genuinely fun, and it is the exact move you just learned aimed at a real target. If you catch the bug, a lifetime of harder puzzles is waiting at ctftime.org.
What you built
Step back from that last bug and look at the whole term. Every tool in this course existed to automate something tedious, and that was never an accident. It is the oldest idea in hacker culture, and it comes as a short creed:
- Laziness is good.
- Boredom is evil.
- So automate the boring stuff.
Behind every project was a boring task we refused to do by hand, and look at what refusing produced. You built and published a real webpage from raw HTML and CSS (Project 0). You wrote a program that turns Markdown into HTML, a compiler of your own (Project 1). You sent Python out to read pages you did not write and pull data out of them by the thousands (Project 2). You built a tool that answers questions about your own documents, a small ChatGPT you control (Project 3), and then gave it hands to act on its own (Project 4). And you finished by cloning Twitter (the final project), a database-backed web application that ties Python, Markdown, HTML, CSS, Jinja2, and SQL into one running program.
That last one is worth sitting with. A working social-media site stops being magic once you have built one. You now know, concretely, how these sites actually work inside, because you wrote every layer yourself.
Here is the honest part: this is hard, and it stays hard. Writing the code is the small part of the job. Figuring out why the code you already wrote does not work is where the hours go.
The bar is unforgiving in a way few other fields share: code that is almost right is usually worthless, because one wrong character can make a whole program fail. You will spend a lot of your life as a programmer in that fourth panel, and it does not fully go away with experience. The good news is that it is learnable. Hard is not the same as impossible, and everyone who writes software well was once exactly where you are, staring at an error they did not understand. It also pays well for precisely that reason; if you are curious what “well” means, levels.fyi lists real salaries, and the skill you started building this term is the one being paid for.
And notice the through-line in all of it. None of these projects was about breaking things. Hackers build things. The breaking, when it happens, is in service of building something sturdier.
Where to go next
There is a whole side of computer science we barely touched. Everything we did was practical: make the thing work. The deeper questions are different, and they turn out to be closer to math than to the code you have been writing.
Real computer science asks things like: how do you prove a program is correct, instead of testing it until you stop finding bugs? What can no computer ever do, no matter how fast or how clever it is? These questions have precise and often surprising answers, and the surprising part is how much of each answer is plain mathematics:
The astronaut is only half joking. Push on any deep question in computing and you eventually hit math holding it up. The next course, usually data structures and algorithms, is where this begins: code that runs is no longer enough, and you learn to make it run well, with the tools to say exactly how well. From there the field opens into the theory of computation, machine learning, systems, and security, and the further in you go, the more it rhymes with mathematics. If the labs frustrated you but the moment the green checkmark finally appeared felt good, that feeling is the one the rest of the field runs on.
We would genuinely like to see you in those later classes.
Looking forward
That is the last of the material. Not a tool this time, but a habit. You now reach for code when a task is repetitive, where a few months ago you would have done it by hand or assumed it could not be automated at all. That instinct is the real thing this course was for, and it outlasts any single language or framework.
So go automate something boring. Scrape the data you keep copying by hand, script the rename you do every week, build the small site you have been meaning to build. You have every tool you need to start, and looking up the ones you do not is the job.
Thanks for a good term. Grab a byte.