• Agents
  • Pricing
  • Blog
Log in
Get started

Security for apps built with AI. Paste a URL, get a report, fix what matters.

Product

  • How it works
  • What we find
  • Pricing
  • Agents
  • MCP Server
  • CLI
  • GitHub Action

Resources

  • Guides
  • Blog
  • Docs
  • OWASP Top 10
  • Glossary
  • FAQ

Security

  • Supabase Security
  • Next.js Security
  • Lovable Security
  • Cursor Security
  • Bolt Security

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • Imprint
© 2026 Flowpatrol. All rights reserved.
Home/OWASP Top 10/Web Top 10/A09: Security Logging and Monitoring Failures
A09CWE-778CWE-117CWE-223CWE-532

Errors that vanish in production
Security Logging and Monitoring Failures

The bug where the attack worked, the logs are empty, and nobody notices until a customer emails support.

One of the most under-reported categories — the bugs that hide other bugs.

Reference: Web Top 10 (2021) — A09·Last updated April 7, 2026·By Flowpatrol Team
Security Logging and Monitoring Failures illustration

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.

  • 1
    Slow walk
    They enumerate 5,000 order IDs over six days, spaced out across different IPs so the traffic looks like normal usage.
  • 2
    Trigger errors quietly
    A 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.
  • 3
    No alert fires
    There is no anomaly detection, no 500-count alarm, no per-user request counter. The infrastructure dashboard shows CPU at 4%.
  • 4
    Exfil complete
    A 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

    Vulnerable — silent catch-all
    // 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 });
      }
    }
    Fixed — structured and alertable
    // 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

    Glossary

    Missing Audit Trail (Insufficient Logging)Sensitive Data Exposure (Information Disclosure)

    What we find

    data exposure

    References

    • A09: Security Logging and Monitoring Failures — official OWASP entry
    • OWASP Top 10 for Web Applications (2021) — full list
    • CWE-778 on cwe.mitre.org
    • CWE-117 on cwe.mitre.org
    • CWE-223 on cwe.mitre.org
    • CWE-532 on cwe.mitre.org

    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