Web App Security Checklist
April 27, 2026•1,310 words
High Importance
1. Authorisation Failures
- All auth to be done solely in the backend
- All endpoints must have authN and authZ, anonymous routes must have a carve out.
- IDOR: Just because a role has access to an endpoint doesn't give it unrestricted access to all objects via that endpoint. eg
- Admin's that have access to
createUserAccount(username, role)should not be allowed to passrole=SuperAdmin. Following should not be allowed:- Admin creating SuperAdmin
- Admin self escalating to SuperAdmin
- Admin updating someone to SuperAdmin
- Admin deleting a SuperAdmin
- Admin modifying a SuperAdmin
- A User role having access to
readPost(id)so they can read their own post must not allow them to read private posts from others simply by passing their ids.
- Admin's that have access to
- Permissions must update instantly after change (eg. role update)
- Session and other token deactivation after user deletion must be instant
2. Local Login
- Proper hashing, salting
- Only use
Argon2id,scryptorbcrypt.PBKDF2-SHA256may be used with 600k iterations on legacy systems.
- Only use
- Length checking (>8)
- Password reset:
- should generate a short-lived cryptographically random string or a uuid.
- It should not indicate whether email exists or not.
- Invalidate all active sessions for that user
- Avoid local login if possible
3. HTML Sanitisation/XSS Prevention
- Safe DOM input methods:
innerTextandtextContent
- Dangerous dynamic functions:
(eval|Function|HTMLScriptElement)\(getElementById().onclick,getElementById().onmouseovergetElementById\(
- Dangerous DOM sinks
(outerHTML|innerHTML|markupstring|@Html\.Raw|\|raw|eval\(|document\.write|document\.domain|onevent|dangerouslySetInnerHTML|insertAdjacentHTML|window.location)
- Dangerous jQuery sinks
(add\(|after\(|append\(|animate\(|insertAfter\(|insertBefore\(|before\(|html\(|prepend\(|replaceAll\(|replaceWith\(|wrap\(|wrapInner\(|wrapAll\(|has\(|constructor\(|init\(|index\(|jQuery.parseHTML\(|\$\.parseHTML\(|ng-app)
4. Security Headers/CORS policy
- If Backend and Frontend are served on separate origins they must both have headers set.
- CORS should be disabled unless required (such as if the frontend and backend are on separate origins). Never reflect
Originheader. - General HTTP header requirements:
x-frame-options: DENY,strict-transport-security: max-age=63072000; includeSubDomains; preload,x-xss-protection: 0,x-content-type-options: nosniff,x-dns-prefetch-control: off
- Content security policy requirements:
default-src 'none'or'self'script-src:- Should never include
'unsafe-inline', should ideally not include'unsafe-eval'or'self'or'data'. - When adding URLs to this directive, ensure the most specific URL is entered. eg. Never enter https://cdn.jsdelivr.net/ as all paths under that origin will be allowed, instead use https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js (individually to be done for all required packages).
- Ideally use
strict-dynamicwith nonces - Ideally use trusted types
- Should never include
object-src 'none'
- Use SRI on imported external JS
- Should pass https://csp-evaluator.withgoogle.com/ and https://securityheaders.com/
5. File Upload Validations
- To be done solely in the backend
- Randomise filenames
- If filenames must be kept, it should be sanitised from path traversal characters: forward/backwards slash and dot as a minimum. Length checking may be necessary if host OS has limitations.
- Limit filesize
- Limit allowed extensions, may further check magic bytes
- Strip EXIF data from images for added privacy of users
- Ideally use a virus scanner
6. Hardcoded Secrets
- Have secrets ever been committed to repo? If so, remove and revoke immediately
- No API keys, db passwords, private certs etc. allowed on both the codebase, or browser (such as saving passwords in web storage)
.gitignoreincludes.env/appsettings.json
7. Cookie Security Settings
HttpOnly,Secure,SameSiteall set- For SPAs that can't set
HttpOnly, security headers and HTML sanitisation must be very thorough.
- For SPAs that can't set
Pathshould be specified if there are multiple applications on the same originDomainmust be left blank unless specifically required
8. SQL Injection
- All queries must either be parameterised or use an ORM.
- Never construct raw SQL from user-controlled input (such as
ExecuteSqlRawAsync()in .NET) - Minimise database application's privileges on the host machine, disallow certain commands such as
DROPfrom the user account
9. Encryption
- When encryption is necessary, use
AES-GCMorCCMwith a cryptographically safe randomIVparameter that is unique per operation. - If data authenticity is not required
AES-CTRorCBCmodes may be used. The hash of the encrypted data may be used as a form of authenticity. - Never store hardcoded keys in codebase, never reuse
IVornonceparameters, never useAES-ECBmode.
Medium Importance
1. Docker
- Keep Docker engine up-to-date
- Only install production dependencies (
--omit dev) and explicitly setNODE_ENVto production - Docker Compose
- Add
security_opt: - no-new-privileges:true
- Add
- Dockerfile
- Base image must be currently supported, LTS preffered.
- Use a minimal version of the image (such as
alpine) rather than full-sized ones. - Add
USER non-rootwherenon-rootis a user that lacks root privileges. (for Node.js images this isnode)
2. Entra/OIDC Login
- Only
suboroidto be used for identifying users, emails cannot be used- For OIDC flows with multiple authentication mechanisms (ie. Google and Microsoft):
sub+isscombined is the only identifier that can be used to identify users.
- For OIDC flows with multiple authentication mechanisms (ie. Google and Microsoft):
- Usage of implicit flow (
idtokenin .NET), is not recommended. Switch to auth code flow.- If must use idtoken, check expiration and signature of JWT.
PKCE SHA256must be enabled for implicit flow.
3. Request Size Controls
- Application-wide maximum request size must be set
4. Rate-Limiting
- Implement global rate-limiting or at least for critical endpoints (login/signup)
- Correct usage of
X-Forwarded-For:- Get the last IP if 1 proxy used, second last if 2 proxies etc. Never trust first IP in the list!
.*(x-forwarded-for|x-real-ip|ipaddress)
5. Info Leak Through Logs and Errors
- Client should never receive full error stacks or error messages from the application.
- Secrets such as user passwords or session tokens should never be logged.
(log|warn|error)\(.*(.*\$|{|[+,])`[=({$]\s*(err|ex)\S*\.
6. Open Redirect
- Application should never trust URLs taken from the request for redirection.
- eg. The url
example.com/login?returnURL=https://attacker.comcan redirect users to a malicious website if the application doesn't verify redirects which can allow phishing. - Can be limited to paths under the same origin by enforcing
returnURLstart with a forward slash, or not allowing it to start withhttpsetc. (returnUrl|next|redirect)
- eg. The url
7. Command Injection
- Never take user input into an OS command (including user editable fields such as filenames or even usernames).
6. Dependancies
- Framework must be currently supported, LTS preferred.
- Routine
npm auditruns, or part of CI/CD chain. - No high+ vulnerability in codebase, unless it doesn't affect the application
- Lockfile must be present
Low Importance
- Unsafe deserialisation
JSON.parse(data)
- Disable Source Maps in Production
- Use strong random for cryptographic operations that need it
Math.random()orSystem.random()are unsafe.
- Misconfiguration (tsconfig, next.config.js etc.)
- User Enumeration
- Return 401 Unauthorised before 404 Not Found (check auth before running query)
- Forgot password functionality should not leak whether email exists
- Lack of Input Sanitisation
- Must be done in backend
- Global error catching, restart after unrecoverable crash.
- SSRF: Prevent application from sending arbitrary requests to arbitrary websites.
- Timing analysis vulnerable comparison
- API keys or passwords must always use timing safe comparison
- SSL must always be enabled even for server-side requests
Programming Problems
- try-catch either swallowing or not handling properly
catch\s*\(.*\)
- [JS/TS] using
==/!=instead of===/!==[^!=]={2}[^=]
- [.NET] public/protected/private classes/variables must be set with lowest access required.
Mobile
- Disable cleartext traffic at
network_security_config.xml - Enable ProGuard/R8
- SSL Pinning may be enabled
- iOS:
NSAllowsArbitraryLoads,NSAllowsLocalNetworking
Run
- [JS/TS] Linter
npx eslint . --ignore-pattern "**/*.js" --quiet
- SCA, aikido
npm auditdotnet list package --vulnerable- https://security.snyk.io/
- SAST, semgrep, opengrep, DAST
semgrep ci --exclude="*.js" --exclude-rule="javascript.browser.security.insecure-document-method.insecure-document-method" 
- check TLS config
Notes
- Highlighted are regex patterns
- When vulnerability is found, check commit for other code edits