The average breach takes 200 days to detect. Not because the attackers are subtle. Because nobody was looking. A request comes in, something goes wrong, the response is a 500, and the reason for the 500 exists only in the mind of a Node process that already exited.
Security logging and monitoring failures mean an attack can run against your app and leave no trace. Failed logins are not recorded, 500s are not counted, suspicious patterns are not alerted on. Every other bug in this list gets louder when nobody is watching — this is the category that makes the other nine survivable for the attacker.
What your AI actually built
You asked for an app, and the model shipped one. Routes, handlers, database calls, a deploy config. Every path works on the happy case. The errors you tested during development showed up in your terminal just fine.
What it didn't ship was a place for errors to go in production. console.log writes to a container log that rotates after a day. There is no structured logger, no error tracker, no audit trail of who did what. When something breaks, you hear about it from a user on Twitter.
Security events get the worst of this. Failed logins, access denials, suspicious payloads — the things you most want to know about — are the exact things the app silently swallows, because 'swallow the error and return 401' is the idiomatic tutorial pattern.
How it gets exploited
An attacker found a BOLA bug on /api/orders/:id last Tuesday. They are patient.
- 1Slow walkThey enumerate 5,000 order IDs over six days, spaced out across different IPs so the traffic looks like normal usage.
- 2Trigger errors quietlyA handful of requests hit deleted records and return 500. The error is swallowed by a catch-all handler that returns "Server error" and logs nothing.
- 3No alert firesThere is no anomaly detection, no 500-count alarm, no per-user request counter. The infrastructure dashboard shows CPU at 4%.
- 4Exfil completeA week later the attacker has every order. You find out when a journalist asks for comment on the dump that just appeared on BreachForums.
The breach itself was an A01 bug. The reason it ran for a week undetected is this one — nothing was watching, and nothing was writing anything down.
Vulnerable vs Fixed
// app/api/orders/[id]/route.ts
export async function GET(req, { params }) {
try {
const order = await db.order.findUnique({ where: { id: params.id } });
return Response.json(order);
} catch (err) {
// Swallowed. Gone forever.
return Response.json({ error: 'Server error' }, { status: 500 });
}
}// app/api/orders/[id]/route.ts
import { logger } from '~/lib/logger';
import { reportError } from '~/lib/errors';
export async function GET(req, { params }) {
const userId = req.headers.get('x-user-id');
try {
const order = await db.order.findUnique({ where: { id: params.id } });
logger.info({ event: 'order.read', userId, orderId: params.id });
return Response.json(order);
} catch (err) {
logger.error({ event: 'order.read.failed', userId, orderId: params.id, err });
reportError(err, { userId, route: 'orders.get' });
return Response.json({ error: 'Server error' }, { status: 500 });
}
}A structured log on both the happy path and the error path, and an error-reporter that forwards to something that can page a human. The response is the same to the caller — the difference is that you now know the request ever happened.
A real case
Equifax breach ran undetected for 76 days
Attackers exploited a known Struts CVE in May 2017 and had uninterrupted access to 147 million records until late July, because an expired TLS certificate had silently disabled the IDS that would have caught them.
Related reading
References
See what your app is not telling you.
Flowpatrol stress-tests your logging and alerting by running loud attacks and measuring the response. Five minutes. One URL.
Try it free