OWASP Top 10:2025 — A09 Security Logging and Alerting Failures for AI-Generated Code

Written by the Rafter Team

Your auth route catches every exception, logs "login failed" with no IP, no user ID, no timestamp beyond what your hosting provider stamps on stdout — and returns a clean 401. Someone runs 40,000 credential-stuffing attempts against it overnight. You find out three weeks later when a customer emails you that their account is posting spam. That's A09. The attack worked because nothing was watching, and nothing was watching because the LLM that wrote your auth route was optimizing for "endpoint returns the right status code," not "operator can reconstruct what happened."
What A09 actually is
A09 covers the gap between something bad happening and somebody noticing. Concretely — security-relevant events (logins, password resets, permission changes, payment attempts, admin actions, failed authorization checks) either aren't logged at all, are logged without enough context to be useful, are logged only to ephemeral stdout that disappears when the container restarts, or are logged somewhere with no alerting on top. The canonical example — your /api/admin/* routes log nothing when a non-admin hits them with a forged JWT, so the attacker enumerates your admin surface for a week and you never see a single line about it.
It's not glamorous. It is the reason the average breach dwell time is still measured in months.
Why AI-generated code trips on it
LLMs are trained on tutorials and example repos. Tutorials show the happy path. Example repos catch exceptions with console.error(err) and move on. So the code you get back tends to:
- Wrap auth and payment handlers in a
try/catchthat logs the error message and nothing else — no actor, no resource, no request ID, no outcome. - Treat
4xxresponses as boring and skip logging them entirely, which is exactly backwards —401and403spikes are the signal. - Use
console.log/printand assume "the platform handles it," which is true for capturing the line and false for ever alerting on it. - Never emit structured events. You get free-text strings that no log aggregator can usefully group.
- Skip logging on successful sensitive actions — password resets, role changes, data exports — because nothing went wrong, so why log it. Those are exactly the events you need an audit trail for.
Next.js API routes and Express handlers are the worst offenders in our scans, because the idiomatic LLM output is a thin handler that either succeeds silently or throws into a generic error boundary. Supabase Edge Functions and Cloudflare Workers are close behind — short-lived, ephemeral, and the default logging is "good luck."
The fix on agentic CLIs (Claude Code, Codex)
Don't ask for "add logging." You'll get console.log calls sprinkled into the happy path. Ask for an audit surface. Something like:
For every route under /api/auth, /api/admin, and /api/billing, emit a
structured JSON log line with: timestamp, request_id, actor_id (or "anon"),
route, method, outcome (success|denied|error), and reason. Log on success
AND on failure. Do not log secrets, tokens, passwords, or full request
bodies. Send to <your log sink — Axiom, Better Stack, Datadog, Logtail>.
Then add an alert rule for: >20 "denied" events from one IP in 5 minutes,
and any "success" on a role-change or password-reset event.
That prompt makes the model do the boring half of the work — picking the fields, wiring the transport, and writing the alert rule — instead of guessing what you meant. If you're working from a Rafter finding, hit Copy-for-AI on the issue and paste it straight in. The prompt comes pre-loaded with the file path, the missing event, and the fields we expect, so the agent edits the right lines instead of inventing a logger module from scratch.
One more thing — after the agent makes the change, ask it to grep the diff for console.log, print(, and bare catch (e) {}. Those are the giveaways that it reverted to its training data.
The fix on opinionated platforms (base44, Greta, OpenClaw)
You can't always hand-edit the handler, so you have to push the platform's agent to do two things explicitly — because it won't volunteer either.
First — tell it what to log, in business terms. "Every time someone logs in, resets a password, changes a role, makes a payment, or hits an admin page, record who did it, when, from where, and whether it worked. Record failures the same way." Most builders will surface this as an "audit log" or "activity log" feature and wire it for you. If the platform refuses or hides the implementation, that itself is a finding — escalate it as a feature request and assume you have no audit trail until proven otherwise.
Second — tell it where to send alerts. "When the same email fails login more than ten times in five minutes, send me an email / Slack / Discord message." Most opinionated platforms have a webhook or notification primitive. If yours doesn't, the next-best thing is exporting the audit log to a third-party sink (Better Stack, Axiom, or even a Google Sheet via Zapier) and putting the alert there. Anything is better than logs that live and die inside the platform's black box.
The trap on these platforms is assuming "they handle security" means "they tell me when security events happen." It doesn't. It means the login form has a rate limiter. The watching part is still on you.
See also
- A07 Authentication Failures — the events you most need to log are almost all auth events.
- A08 Software or Data Integrity Failures — without logging, integrity violations are invisible until the data is already wrong.
- A10 Mishandling of Exceptional Conditions — the cousin of A09. Swallowed exceptions and missing logs are the same failure with two names.