“It’s an internal app, only employees use it.” I hear that before almost every audit, and it almost always ends the same way: any logged-in user can see data they shouldn’t. In HR, procurement or audit systems that means salaries, supplier terms or whistleblower reports.
1. Changing the ID in the URL (IDOR)
/api/requests/6511a... returns the request without checking it’s yours. Put the user’s scope in the query, not in an if afterwards:
const doc = await this.model.findOne({ _id: id, ...this.scope.for(user) });
if (!doc) throw new NotFoundException(); // same response whether it doesn't exist or isn't yours
Return 404 in both cases; different responses reveal which IDs exist.
2. Filters that only exist in the frontend
The table shows “my site”, but the endpoint returns everything and the browser filters. Security filters live on the server.
3. List protected, detail not (or vice versa)
GET /items is scoped, but GET /items/:id, /items/:id/file or /items/export aren’t. Every route returning data uses the same scope rule, centralized in one service.
4. Exports leaking columns
The screen hides “salary”, the Excel export includes it. Remove sensitive columns server-side.
5. Mass assignment
“Update my profile” saves the whole client object and someone adds "role": "admin". Use whitelisted DTOs:
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }));
6. Guessable file URLs
Attachments served as static files without a session. Serve them through an endpoint that checks permissions and logs downloads.
7. Delegation without a ceiling
Managers can delegate more than they have. Nobody should grant a broader scope than their own.
8. No audit log
Record who did what, on which record, when and from where; for sensitive data, reads too.
My method: the failing test first
- Write a test that reproduces the attack (user A requests user B’s record).
- Confirm it fails (the hole exists).
- Fix it.
- The test passes and stays in the suite so the hole never comes back.
Have an internal system that never went through a review like this? Let’s audit it.