Chapter 8 of 8 · 5 checks · 3 min
Logging, errors and file uploads (A09, A10)
You cannot respond to an attack you never see, and an error that fails open can be worse than the bug that caused it. File uploads close the list because they mix several of these risks.
Log sign-ins, permission failures and admin actionsMust
Why it matters and how to check
The 2025 list renamed A09 to Security Logging and Alerting Failures, because logs nobody watches do not help. Record failed and successful sign-ins, password and MFA changes, access denied responses and admin actions, with a timestamp, user id, IP and request id. Never log passwords, tokens or full card numbers.
To verify, perform each of those actions in staging and find the matching log lines. Search your logs for
password,authorizationandtokenvalues to confirm they are redacted.Alert on bursts of failures and unusual admin activityShould
Why it matters and how to check
Logs become useful when a spike reaches a person. Set alerts for things like many failed logins from one IP, a wave of 403 responses, a new admin being created, or a large export. Route them to a channel someone reads.
To verify, trigger one alert on purpose in staging, such as 30 failed logins in a minute, and confirm a notification arrives within the time you expect. Write down who owns each alert. An alert with no owner is the same as no alert.
Fail closed when a security check throwsMust
Why it matters and how to check
A10, Mishandling of Exceptional Conditions, is new in 2025. The classic bug is a try and catch around a permission or payment check that logs the error and carries on, granting access. When a check cannot complete, because the database timed out or a token failed to parse, the safe default is to deny.
To verify, read the error handling around your authorization, payment and validation code. Then simulate a failure, for example by pointing the auth service at a wrong URL in staging, and confirm protected routes return an error instead of data.
Example
TypeScript try { allowed = await canEdit(user, doc); } catch (err) { logger.error({ err, userId: user.id }, 'permission check failed'); allowed = false; // never default to true }Return generic error messages and keep details in logsShould
Why it matters and how to check
Stack traces, SQL errors and internal paths in responses help attackers map your system. Return a short message and a request id to the client, and log the full error on the server with that same id so support can find it.
To verify, force a few errors: malformed JSON, an invalid id format, a missing required field and an unhandled exception. Each response should be a clean message with a status code and a request id, and searching the logs for that id should show the full detail.
Validate uploads by allowlist, size and content, and store them safelyMust
Why it matters and how to check
The OWASP File Upload Cheat Sheet treats the client's
Content-Typeas untrusted. Allow only the extensions you need, check the file signature as well, set a size limit, rename files to a random id, and store them outside the web root or on separate storage. Only signed-in users who need upload should have it, and scan documents for malware where you can.To verify, upload a file renamed from
.phpor.htmlto.jpg, a file over the size limit and a file named../../test.jpg. All should be rejected or safely renamed.Do
- Generate the stored file name yourself
- Limit decompressed size for archives
Avoid
- Trusting the extension or Content-Type sent by the browser