A09 Security Logging and Alerting Failures
You cannot respond to what you cannot see. Logs without alerts are an archive; alerts without good logs are noise. Both must exist, and neither may be injectable or full of secrets.
What it is
This category covers missing or poor security logging, monitoring and alerting: logins and access-control failures that are not recorded, logs that live only on the machine an attacker controls, events with no alert behind them, and logs that leak sensitive data or can be forged by user input.
It is A09 in :2025, with the name stressing alerting: a log nobody is paged about is not a control.
How the attack works
- A credential-stuffing run of thousands of failed logins is not logged, or is logged and never reviewed.
- An attacker with a foothold deletes local logs; with no central copy, the trail is gone.
- : a username containing a newline writes a fake "login succeeded" line.
- Logs record passwords, tokens or card numbers, turning the log store into the richest target.
Vulnerable code
Free-text logging with raw user input, a secret in the message, and no record of the failure that matters.
def login(email, password):
print(f"login attempt {email} / {password}") # secret in the log, newline injection possible
user = authenticate(email, password)
if not user:
return None # failure not recorded
return userconsole.log("login failed for " + req.body.email + " with token " + req.headers.authorization);The fix
Log security events as structured data with stable field names: who, what, where from, outcome, and a . Never log secrets; encode user input so it cannot forge lines. Ship logs off the host immediately, and attach alert rules to the events that matter (spikes in failures, privilege changes, access denied on sensitive objects).
import json, logging
log = logging.getLogger("security")
def event(action, outcome, **fields):
log.info(json.dumps({"action": action, "outcome": outcome, **fields})) # json.dumps escapes newlines
def login(email, password, ip, request_id):
user = authenticate(email, password)
if not user:
event("login", "failure", email=email, ip=ip, request_id=request_id) # no password, no token
return None
event("login", "success", user_id=user.id, ip=ip, request_id=request_id)
return userreq.log.warn({ action: "login", outcome: "failure", email: req.body.email, ip: req.ip }, "auth");
// pino redaction keeps secrets out even if someone logs the whole object
const logger = pino({ redact: ["req.headers.authorization", "*.password", "*.token"] });- alert: LoginFailureSpike
expr: sum(rate(auth_events_total{action="login",outcome="failure"}[5m])) > 5
for: 5m
labels: { severity: page }
annotations:
summary: "Login failures above baseline: possible credential stuffing"Prove the fix
Test that the event is emitted with the right fields, that no secret appears, and that input cannot forge a line. Then test the alert path itself: replay a burst and confirm someone is notified. An untested alert is a hope.
def test_failed_login_is_logged_without_secrets(caplog):
with caplog.at_level("INFO", logger="security"):
login("bob@x.io\nlogin success", "hunter2", "203.0.113.9", "req-1")
text = caplog.text
assert "hunter2" not in text # no secret
assert text.count("\n") == 1 # the injected newline did not create a line
assert '"outcome": "failure"' in text
def test_alert_fires_on_burst(alert_harness):
for _ in range(200):
login("bob@x.io", "bad", "203.0.113.9", "r")
assert alert_harness.fired("LoginFailureSpike")Checklist
- Log: authentication, access-control failures, input-validation failures, privileged actions, and changes to security configuration.
- Use a consistent structured format; include time, actor, action, target, outcome, source and a correlation id.
- Send logs to a central, append-only store quickly; restrict who can alter or delete them.
- Do not log secrets, tokens or full card/ID numbers; encode untrusted input.
- Define alerts and an owner for each; write the response in a runbook; test the alert path.
- Set retention that matches legal and investigation needs.
connections on the map
- A09 Security Logging and Alerting FailuresrecordsA10 Mishandling of Exceptional Conditions
- A09 Security Logging and Alerting Failuresdetects abuse ofA01 Broken Access Control
updated on 14/10/2026 · sources to go deeper · report an error
in other domains
- Detection engineeringsecopsthe log becomes versioned detection
Study before
Unlocks
test what you learned
Loading questions…