Security Logging & Monitoring Failures
Also known as: Insufficient logging and monitoring, Logging gaps, Detection gaps, A09:2021
A weakness in which security-relevant events are not recorded, are recorded without useful detail, or are never reviewed or alerted on, so attacks against an application go unnoticed and uninvestigated.
How it works
Security logging and monitoring failures are gaps in an organisation's ability to see what is happening. Security-relevant events are not written down, the records lack the detail needed to investigate, nobody watches them, or nothing triggers when behaviour turns suspicious. The weakness does not break the application itself; it lets every other attack succeed quietly and last longer.
Typical gaps are easy to list. Failed logins, access-control denials and server-side validation failures are not logged. High-value actions such as payouts, role changes and exports leave no trail. Log lines carry no user, source address, timestamp in a consistent clock, or correlation id, so events cannot be joined. Logs sit only on the machine that produced them, where an intruder can alter or delete them, and no alert exists for bursts of failures or unusual access.
Fixing it is a design task. Decide which events matter, record each with who, what, when, where and outcome, send them in a structured form to a central store such as a SIEM, and write alert rules for patterns worth a human's attention. Protect integrity with append-only or write-once storage and restricted access, keep clocks synchronised, set retention to meet both investigation and regulatory needs, and avoid logging secrets or excess personal data.
Logging only pays off when someone acts on it. An escalation path, an owner for each alert and periodic tests of coverage, such as safely generating a known event and confirming it is seen, turn raw records into detection. Industry data repeatedly shows that long detection times, not clever exploits, drive the cost of breaches.
Walk through it
- 1Read the handler
- 2Name the unlogged event
- 3Collect and alert
- Make every event investigable
- Prove coverage and set response
The portal had an incident that the team only learned about from a customer. As the security engineer, read the login handler and note what it records about events that matter to defenders.
1router.post("/login", async (req, res) => {2 const user = await db.users.findByEmail(req.body.email);3 if (!user || !(await verify(req.body.password, user.hash))) {4 return res.status(401).send("Invalid credentials");5 }6 req.session.user = user;7 res.redirect("/account");8});Spot it
- Security events in an incident timeline that exist only in application data, with no matching log record.
- Authentication and authorisation failures absent from every log source, or recorded without user, source address or correlation id.
- Logs stored only on the producing host, or writable by the application account.
- No alert rules or owners for authentication, access-control or high-value action events.
- Inconsistent timestamps or time zones across sources, and retention shorter than the time attacks typically take to be found.
Before: unstructured application log
Oct 11 09:14:02 web3 portal: login failed
Oct 11 09:14:02 web3 portal: login failed
Oct 11 09:14:03 web3 portal: login failedAfter: structured security event
ts=2026-10-11T09:14:02.411Z event=auth.login outcome=failure user=u_4821 src_ip=203.0.113.50 req_id=7f3a9c reason=bad_password app=portal
ts=2026-10-11T09:14:02.903Z event=auth.login outcome=failure user=u_5190 src_ip=203.0.113.50 req_id=7f3a9d reason=bad_password app=portal
ts=2026-10-11T09:14:09.120Z event=authz.denied outcome=failure user=u_5190 src_ip=203.0.113.50 req_id=7f3aa1 resource=/admin/export app=portalsplSplunk: coverage check for authentication events
index=app sourcetype=portal earliest=-24h
| stats count by event, outcome
| append [| makeresults | eval event="auth.login", outcome="failure", count=0]
| stats sum(count) as total by event, outcomeA zero for auth.login failure on a busy application means the event is not being logged, not that nobody failed.
kqlKQL: many failures from one source across accounts
PortalEvents
| where TimeGenerated > ago(15m) and event == "auth.login" and outcome == "failure"
| summarize failures = count(), accounts = dcount(user) by src_ip
| where failures > 20 and accounts > 5Tune thresholds to normal traffic and add an allow-list for known corporate egress addresses.
Stop it
Log security-relevant events with enough context to investigate
Record authentication successes and failures, access-control denials, server-side input-validation failures and high-value actions. Each event needs who, what, when, where, outcome and a correlation id, in a consistent structured format, without secrets or excess personal data.
Centralise logs and alert on suspicious patterns
Forward events to a central platform such as a SIEM so they can be searched and correlated across systems, and define alert rules with named owners for patterns such as bursts of failures or privileged actions at odd hours.
Protect integrity, set retention and prepare to respond
Use append-only or write-once storage, restrict who can change logs, synchronise clocks, keep records long enough for investigations and regulation, and maintain an escalation path with periodic audits of monitoring coverage.
Emit structured security events from the login handler
Vulnerable
router.post("/login", async (req, res) => {
const user = await db.users.findByEmail(req.body.email);
if (!user || !(await verify(req.body.password, user.hash))) {
return res.status(401).send("Invalid credentials");
}
req.session.user = user;
res.redirect("/account");
});Hardened
router.post("/login", async (req, res) => {
const user = await db.users.findByEmail(req.body.email);
const ok = !!user && (await verify(req.body.password, user.hash));
securityLog.emit({
event: "auth.login",
outcome: ok ? "success" : "failure",
userId: user?.id ?? "unknown",
srcIp: req.ip,
reqId: req.id,
reason: ok ? undefined : "bad_credentials",
});
if (!ok) return res.status(401).send("Invalid credentials");
req.session.user = user;
res.redirect("/account");
});Never log the password or session token. Use the same helper for access-control denials and high-value actions.
Forward logs centrally and alert
Hardened
log_pipeline:
source: /var/log/portal/security.json
destination: siem.contoso-retail.example:6514
transport: tls
alerts:
, name: login-failure-burst
when: count(auth.login.failure) by src_ip > 20 in 5m
notify: soc-oncall
retention_days: 365- Log authentication successes and failures, access-control denials, validation failures and high-value actions.
- Include user, source address, timestamp in UTC, outcome and a correlation id in every security event.
- Forward logs off the host to a central, access-controlled platform over an encrypted channel.
- Make log storage append-only or write-once and separate from the application's own credentials.
- Synchronise clocks with a trusted time source on every system.
- Set retention to cover investigation windows and applicable regulation, such as a year of history.
- Keep secrets, tokens and unnecessary personal data out of logs, and neutralise newlines in logged input.
- Define alert rules with owners, an escalation path, and test coverage on a schedule.
If it already happened
Turn on the missing event logging and central forwarding immediately, and preserve any local logs that still exist before they rotate.
Reconstruct what you can from other sources such as proxies, databases and cloud audit trails, and assess whether earlier attacks went unnoticed.
Deploy alert rules with owners, confirm events appear in the SIEM with a controlled test, and set retention and integrity protections.
Add logging requirements to the secure development checklist, run periodic coverage audits and exercise the escalation path.
Check yourself
1. Why are logging and monitoring failures dangerous even though they do not let an attacker in?
2. Which set of events should an application log at a minimum for security monitoring?
3. What best protects log evidence from an intruder who compromises the application server?