Bug triage
A message lands in the team chat:
I think customers can see each other's orders.
That might be urgent. It might be a misunderstanding. Nobody knows yet.
Bug triage is the work between "someone found something" and "the team knows what to do next."
For security bugs, triage answers four questions:
- Can we reproduce it?
- What harm does it cause?
- How hard is it to use?
- What happens next?
Reproduce the bug
A security finding is a claim until someone can make it happen on purpose.
Start with the vulnerable route:
app.get('/orders/:id', async (req, res) => {
const order = await db.orders.findById(req.params.id)
res.json(order)
})Now write the reproduction:
# Signed in as customer 4102
curl -i https://app.example.com/orders/88213 \
-H "Cookie: sid=<session for customer 4102>"
# Expected: 403 or 404
# Actual: 200, with another customer's orderA good reproduction includes the account, the request, the expected result and the actual result.
"Change the id and it breaks" is a note. The block above is something another developer can run.
Only test where you have permission
Run reproductions against systems you own, maintain, or have written permission to test.
The same request against someone else's app isn't triage.
Write the account, the request, what should have happened and what actually happened. If another developer can follow it without asking questions, you have a useful report.
Explain the impact
Impact means what the person using the bug ends up holding or doing.
For the order bug, the impact might be:
Any signed-in customer can read another customer's delivery address and order history.
That sentence is better than:
IDOR on
/orders/:id.
IDOR is the security name: insecure direct object reference. It means a request includes an id for an object, and the server doesn't check whether the caller is allowed to use that object.
The name helps engineers classify the bug. The impact helps everyone understand why it matters.
Ask:
- What data or action is exposed?
- Whose data or action is it?
- How many people could be affected?
- What could this enable next?
Severity starts with the harm, not the cleverness of the bug.
Security names are useful, and they aren't enough on their own. "Any customer can read another customer's address" tells more people what matters than "IDOR".
Judge exploitability
Impact tells you what the bug can do. Exploitability tells you what it takes to do it.
For the order bug, the attacker needs:
- a normal customer account
- one valid or guessable order id
- one request
That is highly reachable. A bug that needs an admin account, physical access, perfect timing and a victim click is different.
Write the preconditions down. They keep the severity conversation concrete.
| Preconditions | What it means |
|---|---|
| Anonymous | Anyone on the internet can try it. |
| Signed in | A normal account is needed. |
| Privileged role | A staff or admin role is needed. |
| Victim action | Someone else must click or submit something. |
| Guessable id | The target can be found by trying values. |
| Timing window | The bug works only during a narrow moment. |
Anonymous is easier to reach than signed in. Signed in is easier to reach than admin. One request is easier to reach than a chain that needs timing and another person clicking something.
Decide what happens next
Now combine the pieces:
| Field | Example |
|---|---|
| Reproduction | Customer 4102 requests order 88213 and receives another customer's order. |
| Impact | Any signed-in customer can read another customer's delivery address and order history. |
| Exploitability | Normal account, guessable id, one request. |
| OWASP category | A01 Broken Access Control. |
| Next action | Fix before release, add ownership test, check similar routes. |
The OWASP category helps classify the bug. The next action turns the report into work.
For this bug, the fix is to check ownership before returning the order:
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 matching test should prove the bad path, not only the happy path.
A good triage note creates follow-up work
Fix this endpoint.
Add a regression test.
Search for the same pattern in nearby routes.
For this order bug, the decision should be direct: fix before release, add a test, and check nearby routes for the same missing ownership check.
Try it
A bug report lands with one line in it: "The password reset link still works after you've used it."
Triage it. Fill in the five fields: reproduction, impact, exploitability, OWASP category, next action.
Compare your answers
| Field | A reasonable answer |
|---|---|
| Reproduction | Request a reset, follow the link, set a password, then open the same link again. It's still accepted and lets you set another password. |
| Impact | Anyone who sees that link, once, can take the account over later. Reset links sit in inboxes, browser history, and forwarded emails for a long time. |
| Exploitability | No account needed and no guessing. It requires access to the link, so the difficulty is entirely about where that link has travelled. |
| OWASP category | A07 Authentication Failures. The app can't reliably establish who someone is when a spent credential still works. |
| Next action | Invalidate the token on first use, and add an expiry if there isn't one. Then check whether email verification and invite links have the same flaw. |
Two things worth pulling out.
The impact isn't "a token isn't invalidated", it's "someone can take the account over". A field that restates the bug adds nothing; the field is there to say who gets hurt.
And the next action ends by looking sideways. Single-use tokens are usually generated by shared code, so a bug in one flow is very often present in the others. Triage that fixes exactly one route and stops has done half the job.
Where this goes next
Bug triage closes the first section. You can now ask what could go wrong, name common live bugs, and turn a finding into work.
Next, the handbook moves into input and data safety, starting with never trust user input.

