The idea: you can't respond to what you never see

Every other item of the OWASP Top 10 is about stopping an attack. A09:2025 Security Logging and Alerting Failures is about the attacks you didn't stop: if nothing records them and nobody is told, an intruder can stay for months. In many breaches the company hears about it from someone outside (a bank, a customer, a researcher), not from its own monitoring. OWASP renamed the item in 2025, from "Monitoring" to "Alerting", to say that logs nobody acts on are not enough.

OWASP maps it to five CWEs, all shown on this page:

  • CWE-778 Insufficient Logging: security events are not logged at all;
  • CWE-223 Omission of Security-relevant Information and CWE-221 Information Loss or Omission: some events, or the fields that matter (who, from where), are missing;
  • CWE-532 Insertion of Sensitive Information into Log File: passwords, session ids, card numbers in the log;
  • CWE-117 Improper Output Neutralization for Logs: log injection.
The detection pipeline: the app writes an event, a shipper sends it to an append-only central store, alert rules read the store, and a page reaches the on-call engineer who blocks the attacker. Red marks show where each failure breaks the chain: nothing logged, secrets or forged lines in the log, logs kept only on the app host and rotated, no rules, alerts nobody reads
Detection is a chain: an event, a safe place to keep it, a rule that reads it and a person who is told. Any missing link means the attack is found by someone else.

Reading the canvas: on the top left is the client (mallory's attack script, or alice's browser), with each request and its reply. In the middle is the app, shop.example, with its logger. On the right is alerting: the central log store, three alert rules and the on-call engineer's pager. Below are the app's own app.log (red rows hold a secret or a forged line), the incident summary, and the timeline with attack events above the axis and alerts below it. Pink boxes are mallory's requests, blue ones alice's, grey ones log lines and events. The logging menu picks one of three setups; the attack is the same in all of them.

One attack, three setups

At 09:00 mallory replays five user/password pairs from a list leaked by another site; at 09:01 alice's reused password works; at 09:02 mallory probes /admin; at 09:04 she exports all 5,000 orders through the API; at 09:06 she changes alice's email so the password-reset mail goes to her. (The first two steps are the credential stuffing of A07 Authentication Failures.)

SetupDetectedByTakenEvidence
No logging (Demo 1)day 212a bank tracing card fraud5,000 orders, alice's accountnone
Plain log file, no alerts (Demo 2)day 212the same bankthe samedeleted on day 30 by logrotate
JSON events + alerts (Demo 3)09:01rule R2, a pagenothing: blocked at 09:0310 events, kept 365 days

The plain log file is the common middle case: the app does log, so the box "logging" is ticked in an audit, but the file sits on the app host, holds only logins, and nobody reads it. When the bank calls on day 212, the evidence was rotated away on day 30. Worse, each failed-login line holds the password that was tried, so the log itself is a password list (CWE-532).

Three timelines of the same attack. No logging: attack at 09:00 to 09:06, found on day 212 by a bank. Plain log: the same, and the logs are deleted on day 30. JSON plus alerts: R1 ticket at 09:00, R2 page at 09:01, contained at 09:03, export at 09:04 refused
The attack takes six minutes in every setup; only alerting moves detection from day 212 to minute 1.

What to log, and what never to log

Log every security decision, whether it allowed or refused: logins and failed logins, access-control failures, input-validation failures, high-value actions (exports, payments, changes of email, password or permissions) and admin actions, including the responder's own. Each event needs enough context to investigate it later: when (UTC, synchronized clocks), who (user id), from where (IP, user agent), what (event type, path, object id) and the outcome.

LogNever log
login_fail, login_ok with user and IPthe password, even a wrong one (a typo is one character from the real one: Demo 5)
access_denied with user and pathsession ids, access tokens, API keys
exports, payments, account changesfull card numbers, health data, other personal data the investigation doesn't need
admin and responder actions (ip_blocked by dana)encryption keys, connection strings

In the plain setup the 403 on /admin is not logged at all: to the app it is not an error. That is CWE-223: the events that matter most to security often look normal to the code.

Alerts and time to detect

Logs only help if something reads them while the attack is happening. The JSON setup ships each event to a central log store (a SIEM, or any log platform), where three rules run:

  • R1: 5 failed logins from one IP in 5 minutes → a ticket;
  • R2: a successful login from such an IP → a page to the on-call engineer;
  • R3: access denied on /admin → a ticket.

R2 is the one that matters: failures alone are background noise on any public login, but a success after a burst of failures from the same IP is almost always a takeover. The page reaches dana at 09:01; at 09:03 she blocks the IP and revokes alice's sessions, so the 09:04 export is refused (Demo 3). Time to detect drops from 212 days to 1 minute, and time to contain is 3 minutes. Her own action is logged too.

Log injection

A plain-text logger that pastes user input into a line lets the user write lines. mallory's user name is x%0a09:00 login ok user=admin: the %0a decodes to a newline, so app.log gets a second line saying an admin logged in (Demo 4). An investigator, or a rule matching "login ok", trusts it. The same trick can hide real lines, break a parser, or run script in a web-based log viewer that shows lines as HTML.

plain text:  09:00 login failed pw=guess user=x
             09:00 login ok user=admin               ← forged
JSON:        {"ts":"09:00","ev":"login_fail","user":"x\n09:00 login ok user=admin","ip":"203.0.113.9"}

The fix (CWE-117) is to encode: a structured format such as JSON escapes the newline as \n inside the field, so one request is always one event. If you must write plain text, replace CR and LF, and encode for HTML when the log viewer is a web page.

Keeping logs safe and long enough

A log kept on the app host is lost when the host is rebuilt, rotated after a few days, and editable by the attacker who owns the host. Ship events off the host as they happen, to append-only (write-once) storage that the app's own credentials cannot delete. Keep them long enough to cover how long breaches go unnoticed: months, often a year or more, and what your regulator requires. Use one clock (NTP, UTC) on every host, and a request or trace id in every event so one request can be followed across services (see Distributed Tracing with OpenTelemetry).

Thresholds and alert fatigue

An alert on every failed login would page someone hundreds of times a day, and after a week nobody would look. alice's single typo (Demo 5) is logged but stays at 1 of 5 on R1: no alert. Good rules fire on what a real user rarely does (a success after many failures, a first-ever admin request, a huge export), page only for those, and send the rest to tickets or dashboards. Every page should have a playbook: what to check, how to block, who to tell. Honeytokens help too: a fake admin account or a fake API key that no real user ever touches, so any use of it is an alert with no false positives.

How to prevent it

  • Log every login, failed login, access-control failure, input-validation failure and high-value action, with time, user, IP and outcome.
  • Never log passwords, session ids, tokens or full card numbers; mask what you need to keep.
  • Use a structured format (JSON) and encode every field; one request, one event.
  • Ship logs off the host to an append-only central store; keep them long enough for an investigation.
  • Write alert rules for the attacks you expect (stuffing, takeover, admin probing, mass export), with thresholds that a real user doesn't reach.
  • Make the alerts reach a person, with a playbook; test them by running the attack yourself.
  • Have an incident response plan (for example NIST SP 800-61) and practise it.

OWASP Top 10:2025 series

What the page leaves out

Real attacks are slower and spread over many IPs, so real rules count per account and per network as well as per IP, and the 2-minute response assumes someone is awake. The page leaves out log levels and sampling, log shippers and their buffering (events lost when the host dies), SIEM correlation and anomaly detection, alert routing and escalation, tamper-evident logs (hash chains, signed batches), privacy rules on what may be kept and for how long, client-side and infrastructure logs (firewall, DNS, cloud audit trails), and the cost of storing a year of events.