Security logging
Security logging records the security decisions an application makes, such as sign-ins, denied requests, permission changes and rejected input, as structured events that say who did what, when and where. The events go to a separate store where they can be searched, alerted on and trusted during an investigation.
Load balancer and web server logs show that a request arrived; only the application knows that a support account changed a role in another tenant, or that one user was denied hundreds of times. Without those events you cannot detect the attack or reconstruct what happened. OWASP's Top 10:2025 calls the category Security Logging and Alerting Failures (A09): without alerting, a quick response is very hard.
- Who
- Developers who emit the events, with the platform or security team who run the log pipeline, the alerts and the response
- When
- When a feature adds a sign-in, authorization or administrative path, using the threat model to choose the events and alerts
- How often
- Events added with each such feature; the path from event to alert to responder tested in every incident exercise
- You end up with
- A list of logged events and alerts, structured events in a separate store, and a record of an exercise that used them
Do this
Choose the events from the threat model
List the decisions a responder would need to reconstruct: sign-ins and their failures, MFA and password changes, authorization failures, role and permission changes, administrative actions, rejected input and use of break-glass accounts. ASVS 5.0 section V16.3 asks for authentication operations, failed authorization attempts, attempts to bypass controls and unexpected errors to be logged.
Use one structured format and vocabulary
Emit one JSON object per line with a fixed event name, a UTC timestamp, service and version, actor, target, tenant, outcome, reason and an operation ID that survives retries. Log the decision and the committed effect as separate events. Take event names from a shared list such as the OWASP Logging Vocabulary (
authn_login_fail,authz_fail), and map fields to a schema such as ECS or OCSF.Keep secrets and personal data out
Never log passwords, access tokens, session IDs, encryption keys, connection strings or payment card data; log a hash of the session ID if you need to follow a session. Configure redaction in the logger, then test it with canary values at several nesting depths, because path-based redaction covers only the paths you list.
Let the logger encode untrusted values
Pass user input to the logger as fields, never concatenated into the message. In JSON lines a newline stays inside the string, so a crafted user name cannot forge a second entry (CWE-117). Bound field lengths, and remember that log viewers and CSV exports need their own output encoding.
Ship events to a separate, protected store
Send events over a secure channel to a logically separate system the application cannot modify, such as your central log platform or SIEM, as ASVS 5.0 V16.4.3 asks; NIST SSDF PW.1.3 likewise prefers integrating with existing log management to building your own. Restrict who can read the logs, synchronize clocks, keep event and ingestion times, and alert when collection stops.
Write alerts for what the threat model fears
For each high-impact threat, define the event pattern and the alert: many failed sign-ins for one account, a role granted outside the admin flow, a large export by an account that never exports. Give each alert an owner and a playbook, as OWASP Top 10:2025 A09 recommends, and set thresholds you can act on rather than ones that page all night.
Prove it in an exercise
In a test environment, generate synthetic events, including hostile-looking names, very long values and a stopped log shipper, and follow one alert to a responder who answers a concrete question, such as which account changed a role and whether the change took effect. A dashboard of collected bytes shows ingestion, not usefulness.
Done when
Alerts exist for the events the threat model cares about, and the logs have been used in an exercise.
Mark it in the gap checkCommon mistakes
- Logging the request but not the outcome. A 200 response to
POST /rolesproves a response, not a role change; log the decision and the committed effect, joined by an operation ID. - Concatenating user input into plain-text log lines, so a value containing a newline forges a second entry that looks genuine.
- Logging full request headers or bodies while debugging, which puts bearer tokens and passwords in a store that many people can read.
- Collecting everything and alerting on nothing. Events that nobody reviews or alerts on do not detect an attack.
- Keeping the only copy of the logs on the host or account an attacker controls, where they can be deleted along with the evidence.
- Sampling or dropping security events under load, which leaves gaps when an attack is noisiest.
Which events to log first
The OWASP Logging Vocabulary gives standard event names, so alerts can key on the same words across services. A starting set:
| Category | OWASP vocabulary examples | What the event should answer |
|---|---|---|
| Authentication | authn_login_success, authn_login_fail | Who signed in or tried to, from where, with which factor |
| Authorization | authz_fail, authz_change | Who was denied what, and which rule decided |
| User and privilege changes | user_created, privilege_permissions_changed | Who changed whose access, from what to what |
| Input validation | input_validation_fail | Which field and rule rejected the input, without the raw value |
| System | sys_shutdown | When the service, or its logging, stopped |
Tools and guides
- Fix cards: tested fixes by stack
- Create security logs someone can investigate
Record meaningful decisions with consistent structure, protect the resulting evidence, and prove that important events reach an operational owner.
- Turn runtime events into a tested investigation and containment path
Collect evidence that answers a concrete security question and rehearse the response without destroying the facts needed to investigate.
- Prevent CRLF header injection, log forging and CSV formula injection
Carriage return and line feed separate HTTP header fields, log records and CSV rows, and a leading = turns a spreadsheet cell into a formula. Let the framework write headers, log structured JSON, and neutralize formula triggers in exports.
Standards
- NIST SSDF 1.1
- PW.1 Design Software to Meet Security Requirements and Mitigate Security Risks
- OWASP SAMM 2
- Incident Management
Sources
- OWASP Logging Cheat Sheet
- OWASP Logging Vocabulary Cheat Sheet
- OWASP ASVS 5.0.0: V16 Security Logging and Error Handling. Version 5.0.0.
- OWASP Top 10:2025 A09 Security Logging and Alerting Failures. OWASP Top 10:2025.
- CWE-117: Improper Output Neutralization for Logs. CWE version 4.20.
- NIST SP 800-218: Secure Software Development Framework (SSDF) Version 1.1, task PW.1.3. February 2022.
- pino: redaction
- OWASP SAMM: Incident Management (Incident Detection stream)