How to think about security
A feature can work perfectly and still be unsafe. Imagine a tiny order route:
app.get('/orders/:id', async (req, res) => {
const order = await db.orders.findById(req.params.id)
res.json(order)
})The route reads an id, finds an order and sends it back. If you test it with your own order, it looks fine. Now change the id in the URL. If the server never checks who owns that order, one signed-in customer can read another customer's receipt.
That is the security mindset: don't only ask whether the code works. Ask what someone could make it do.
Security asks a second question: what happens when someone uses it in a way we didn't intend?
Notice the boundary
A trust boundary is the line between code you control and something that can send you a value you didn't choose.
The browser sits on the other side of that line. A user can change the URL, edit a hidden form field, repeat a query parameter, remove a cookie, or skip your front end and send a request by hand.
That makes the browser the wrong place to make decisions you care about.
Browser checks are useful, but they are not the control
Client-side validation helps people fill out forms. It catches typos early and gives a nicer experience.
The server still has to check the value again, because the server is the part that protects the data.
Here is the same order route with the missing decision made explicit:
app.get('/orders/:id', async (req, res) => {
const order = await db.orders.findById(req.params.id)
if (!order || order.userId !== req.user.id) {
return res.status(404).json({ error: 'Order not found' })
}
res.json(order)
})The important line is the comparison between order.userId and req.user.id. The server is saying: this order may exist, but it also has to belong to the person asking for it.
A request is allowed when the server can prove the decision for itself.
Put the important checks on the server. That is where your app can compare the request with the data it already trusts.
Name what could break
Once you spot a risky decision, give the possible failure a name:
| Question | Security word | Example |
|---|---|---|
| Who can read this? | Confidentiality | A customer reads another customer's order. |
| Who can change this? | Integrity | A customer changes the price before checkout. |
| Can people still use this? | Availability | One request makes the app slow for everyone. |
Together these are called the CIA triad: confidentiality, integrity and availability.
A leaked receipt is a confidentiality problem. A hidden price field is an integrity problem. A search route that lets one request ask for a million records is an availability problem.
Some bugs touch more than one. If one customer can edit another customer's delivery address, they can both see it and change it.
Those questions turn a vague worry into something you can explain. A bug report gets clearer when you can name the harm.
Think from the other side
The phrase "think like an attacker" can sound theatrical. In day-to-day development, it is quieter. Pick one input and ask:
- What if this value is missing?
- What if it is much longer than expected?
- What if it is the wrong type?
- What if it belongs to another user?
- What if the same request is sent thousands of times?
Take a profile form. The page might show a text input for displayName, a hidden userId, and a submit button. The intended path is small: the signed-in person updates their own name.
The security path asks different questions. Why is userId in the form at all? What happens if it points at someone else? Does the server use the signed-in session, or does it trust the submitted field?
Hidden does not mean trusted
A hidden form field is hidden from the page, not from the person sending the request.
If the value matters, derive it on the server from the session or database. Don't ask the browser to send back a decision the server already knows.
The same habit works outside forms. A file upload claims a filename and a content type. A token claims an identity. A webhook claims an event happened. Check the claim at the point where your code is about to depend on it.
That is security thinking in a small, usable form.
Where this goes next
This mindset is the setup for the next part of the course.
Threat modelling asks what could go wrong before a feature ships. OWASP gives you shared names for bugs in live apps. Bug triage helps you decide what to fix first.
Keep the loop small:
- Find the boundary.
- Name what could break.
- Check the claim on the server.

