Web Application Security Checklist: OWASP Top 10 in Practice
A practical web application security checklist built on the OWASP Top 10:2025, plus security headers, secrets, dependencies and tested backups.

A good web application security checklist turns the OWASP Top 10 into concrete checks you can run against your own app: who can access what, how the server is configured, what your dependencies are, and whether you can recover from a bad day. Below is the checklist we use when we review or take over a web application, mapped to the current OWASP Top 10:2025 and extended with the operational items the list does not cover.
What changed in the OWASP Top 10:2025
The OWASP Top 10:2025 is the current edition as of September 2026. It replaced the 2021 list and brought two new categories: Software Supply Chain Failures and Mishandling of Exceptional Conditions. Server-Side Request Forgery (SSRF), which had its own slot in 2021, is now folded into Broken Access Control, and Security Misconfiguration moved up to second place.
| Rank | Category | What it means in practice |
|---|---|---|
| A01 | Broken Access Control | Users reach data or actions they should not (including SSRF) |
| A02 | Security Misconfiguration | Debug mode on, default credentials, open storage, missing headers |
| A03 | Software Supply Chain Failures | Compromised or vulnerable packages, build tools and CI pipelines |
| A04 | Cryptographic Failures | Weak or missing encryption, bad password hashing |
| A05 | Injection | SQL, command, template injection and XSS |
| A06 | Insecure Design | Missing threat modelling, flawed business logic |
| A07 | Authentication Failures | Weak login, session and MFA handling |
| A08 | Software or Data Integrity Failures | Unsigned updates, unsafe deserialisation, untrusted data in trusted places |
| A09 | Security Logging and Alerting Failures | Attacks happen and nobody notices |
| A10 | Mishandling of Exceptional Conditions | Errors that fail open, leak details or leave inconsistent state |
The list is an awareness document, not a complete standard. If you need a formal verification standard, look at the OWASP ASVS. For most small and mid-sized teams, though, working through the checks below closes the majority of real-world gaps.
The OWASP Top 10 checklist, category by category
A01: Broken Access Control
- Deny by default. Every route, API endpoint and file download needs an explicit authorisation check, not just “is logged in”.
- Check ownership on every object lookup.
/invoices/1042must verify the invoice belongs to the current user or tenant (this is the classic IDOR bug). - Enforce authorisation on the server. Hiding a button in the UI is not access control.
- Protect admin routes with separate roles and, ideally, MFA.
- For SSRF: if your app fetches URLs supplied by users (webhooks, image imports, link previews), use an allowlist of hosts and block internal IP ranges and cloud metadata addresses.
In Laravel this usually means policies and authorize() calls; in Express or Fastify it means middleware that runs before every handler. We cover the Laravel side in more detail in our Laravel best practices post.
A02: Security Misconfiguration
- Debug mode off in production (
APP_DEBUG=false,NODE_ENV=production,DEBUG=False). - No default credentials anywhere: databases, admin panels, CMS installs.
- Directory listing disabled, and
.env,.gitand backup files not reachable over HTTP. - Cloud storage buckets private unless they are meant to be public.
- Error pages that do not show stack traces or framework versions.
A03: Software Supply Chain Failures
- Commit your lockfile (
package-lock.json,pnpm-lock.yaml,composer.lock) and install from it in CI (npm ci,composer install). - Run
npm auditorcomposer auditin CI and enable Dependabot or Renovate. - Remove packages you no longer use. Every dependency is attack surface.
- Pin third-party GitHub Actions to a version or commit SHA, and give workflows the minimum
permissionsthey need. - Be wary of typo-squatted package names when adding something new.
A04: Cryptographic Failures
- HTTPS everywhere, with HSTS once you are sure every subdomain supports it.
- Hash passwords with a slow, salted algorithm such as bcrypt or Argon2id. Never MD5 or SHA-1.
- Encrypt sensitive fields at rest where the data justifies it (health data, national IDs, API tokens you store for customers).
- Do not invent your own crypto. Use your framework’s encryption helpers.
A05: Injection
- Parameterised queries or the ORM for all database access. No string concatenation into SQL.
- Output encoding by default in templates (Blade
{{ }}, React JSX, Twig autoescape). Treat every “raw HTML” escape hatch as a code review item. - Never pass user input to shell commands. If you must, use argument arrays, not a shell string.
- Validate input on the server with a schema (Laravel validation, Zod, Pydantic).
A06: Insecure Design
- Spend an hour on a lightweight threat model for new features: what could a malicious user do here?
- Rate-limit anything that costs money or leaks information: login, password reset, SMS, AI endpoints, search.
- Check business rules on the server: negative quantities, coupon stacking, skipping payment steps.
A07: Authentication Failures
- Offer MFA, and require it for admins.
- Rate-limit and monitor login attempts; avoid messages that confirm whether an email exists.
- Regenerate session IDs on login, and set cookies with
Secure,HttpOnlyandSameSite. - Expire password-reset tokens quickly and make them single-use.
A08: Software or Data Integrity Failures
- Do not deserialise untrusted data with native serialisers (PHP
unserialize, Pythonpickle). - Verify webhook signatures (Stripe, GitHub, payment providers) before acting on them.
- Protect your CI/CD pipeline: branch protection on
main, required reviews, and deploy secrets scoped to the deploy job only.
A09: Security Logging and Alerting Failures
- Log authentication events, authorisation failures, admin actions and payment changes.
- Never log passwords, tokens or full card numbers.
- Send errors to a monitoring tool and route alerts to a human who will read them.
- Keep logs long enough to investigate an incident weeks after it started.
A10: Mishandling of Exceptional Conditions
- Fail closed. If the permission check throws, the answer is “no”, not “continue”.
- Wrap multi-step writes in database transactions so a failure does not leave half-finished orders.
- Return generic error messages to users and keep the detail in logs.
- Test the unhappy paths: timeouts from third-party APIs, full disks, expired tokens.
Security headers checklist
Headers are cheap to add and block whole classes of attacks. As a baseline for most web apps:
| Header | Suggested starting value |
|---|---|
Strict-Transport-Security |
max-age=31536000; includeSubDomains |
Content-Security-Policy |
Start with default-src 'self' and add what you need |
X-Content-Type-Options |
nosniff |
Referrer-Policy |
strict-origin-when-cross-origin |
Permissions-Policy |
Disable features you do not use, such as camera=(), microphone=() |
frame-ancestors (in CSP) |
'self' to prevent clickjacking |
On Apache or cPanel hosting you can set them in .htaccess:
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data:; frame-ancestors 'self'"
</IfModule>
Roll out CSP with Content-Security-Policy-Report-Only first, fix what breaks, then enforce it. You can check the result with Mozilla’s HTTP Observatory.
Secrets management
Leaked secrets are one of the most common ways small teams get breached, and it rarely involves a clever attacker.
- Keep secrets in environment variables or a secrets manager, never in the repository.
.envbelongs in.gitignore;.env.examplewith placeholder values belongs in the repo. - Turn on GitHub secret scanning and push protection, or run a scanner such as Gitleaks in CI.
- Use separate keys for development, staging and production.
- Rotate a secret immediately if it was ever committed, even to a private repo, even briefly. Deleting the commit is not enough.
- Give API keys the narrowest scope the provider allows.
Dependency hygiene
Supply chain issues now sit at A03, so treat dependency updates as routine maintenance, not a yearly project.
# JavaScript
npm ci
npm audit --audit-level=high
# PHP
composer install --no-dev --optimize-autoloader
composer audit
A simple rhythm that works: automated PRs for patch and minor updates weekly, a planned upgrade for framework majors, and an immediate response for anything flagged as critical. Keep the runtime itself (PHP, Node.js, Python) on a supported version; an unsupported runtime means no more security fixes.
Backups and recovery
Backups are a security control. Ransomware, a bad migration or a compromised admin account all end the same way if you cannot restore.
- Follow the 3-2-1 idea: at least three copies, on two types of storage, one off-site.
- Back up the database and user uploads, not just the code.
- Encrypt backups and keep them where the production server’s credentials cannot delete them.
- Test a restore at least once a quarter. An untested backup is a hope, not a plan.
- Write down the recovery steps so someone other than the original developer can follow them.
Key takeaways: the short checklist
- Every endpoint checks authorisation on the server, including object ownership
- Debug off, no default credentials,
.envand.gitnot web-accessible - Lockfiles committed,
auditin CI, automated dependency updates - HTTPS with HSTS, passwords hashed with bcrypt or Argon2id
- Parameterised queries and escaped output everywhere
- Rate limits on login, reset, and expensive endpoints
- MFA for admins, secure cookie flags, session regeneration on login
- Webhook signatures verified, CI/CD secrets scoped
- Security events logged and alerts routed to a person
- Errors fail closed and do not leak details
- Security headers set and CSP enforced
- Secret scanning on, secrets rotated after any leak
- Encrypted off-site backups with a tested restore
Security is not a one-time audit; it is a set of habits built into how the app is developed and maintained. If you want a second pair of eyes on an existing application, or help fixing what a review turns up, our maintenance and support work covers exactly this kind of ongoing hardening. Get in touch and tell us what you are running.