Web Security Cheatsheet

Testing Checklist

Use this Web Security reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Scope and authorization first

Before any security testing, get written permission for the specific targets and the specific time window. Testing a system you do not own or have explicit authorization for is unlawful in most jurisdictions, regardless of intent, and "I was only checking" is not a defense.

ItemWhy it must be written down
Exact in-scope hosts, domains, and accountsPrevents touching a neighbor's system on shared infrastructure
Out-of-scope systemsThird-party SaaS usually forbids testing without its own consent
Time window and rate limitsAvoids being indistinguishable from an attack
Permitted techniquesDenial of service and social engineering are usually excluded
A named contact and an abort signalSomeone to call when something breaks
Data handling rulesWhat you may retain, and how you destroy it afterwards

For work on your own product, prefer a dedicated staging environment with synthetic data. If you must test production, use accounts you created and never touch real users' records.

Review checklist by class

Run this against a diff or a feature. One line each, and the corresponding topic in this reference has the detail.

Input and injection

  • [ ] Every SQL query uses bind parameters, and no placeholder is quoted.
  • [ ] SQL identifiers come from a hardcoded map, never from input.
  • [ ] Shell calls pass an argv array with no shell interpretation.
  • [ ] No template, regex, or filesystem path is built from user input.
  • [ ] NoSQL query values are type-coerced before use.
  • [ ] XML parsers have DTD and external entity processing disabled.

Output and XSS

  • [ ] Template auto-escaping is on, and each opt-out has a sanitizer and a comment.
  • [ ] No untrusted value lands in <script>, style, or an on* attribute.
  • [ ] Every template attribute is quoted.
  • [ ] href and src values are scheme-validated, not just encoded.
  • [ ] A nonce-based CSP is enforced with a fresh nonce per response.
  • [ ] object-src 'none' and base-uri 'none' are set.

Access control

  • [ ] Identity is derived from the session or a verified token, never the request body.
  • [ ] Queries filter by owner or tenant rather than checking after loading.
  • [ ] Every method on a resource is protected, not just the one under review.
  • [ ] Writable fields are an explicit allowlist, so mass assignment is impossible.
  • [ ] Nested ids are verified against their parent.
  • [ ] Denials return 404 rather than confirming the object exists.
  • [ ] A test asserts that tenant B cannot reach tenant A's object.

Authentication and sessions

  • [ ] Passwords use Argon2id, scrypt, bcrypt, or PBKDF2 at current parameters.
  • [ ] Session ids regenerate on login and on privilege change.
  • [ ] Logout invalidates the session server side.
  • [ ] Cookies carry HttpOnly, Secure, SameSite, and no Domain.
  • [ ] Login, signup, and reset give identical responses for unknown accounts.
  • [ ] Reset tokens are CSPRNG, single use, short lived, and revoke all sessions.
  • [ ] Rate limiting exists per account and per IP with backoff.
  • [ ] JWT verification pins the algorithm and checks exp, aud, and iss.

CSRF and cross-origin

  • [ ] No state change on GET, HEAD, or OPTIONS.
  • [ ] Every mutation validates a token, a custom header, or Origin.
  • [ ] Missing Origin and Referer on a mutation is a rejection.
  • [ ] CORS uses an exact-match allowlist and sends Vary: Origin.

SSRF and outbound requests

  • [ ] User-supplied URLs are matched against an exact host and scheme allowlist.
  • [ ] The address is resolved once, validated, and connected to directly.
  • [ ] Redirects are disabled or re-validated per hop.
  • [ ] Private, loopback, and link-local ranges are rejected in IPv4 and IPv6.
  • [ ] IMDSv2 or the provider equivalent is required, hop limit 1.
  • [ ] Network egress from the fetching workload is restricted.

Data, secrets, and crypto

  • [ ] No secret in the repo, a client bundle, an image layer, or a log.
  • [ ] Encryption is AEAD with a fresh nonce per message.
  • [ ] No MD5, SHA-1, ECB, DES, or RC4 in a security path.
  • [ ] Tokens, ids, and salts come from a CSPRNG.
  • [ ] Secret and MAC comparisons are constant time.
  • [ ] Certificate verification is enabled in every client.

Files and dependencies

  • [ ] Stored filenames are server-generated and extensions are allowlisted.
  • [ ] Content type is detected from bytes and cross-checked with the extension.
  • [ ] Files are served from a separate origin with attachment and nosniff.
  • [ ] No untrusted input reaches pickle, yaml.load, readObject, or unserialize.
  • [ ] Dependencies are pinned with hashes and scanned in CI.

Configuration and observability

- [ ] Security headers are set at the edge and present on error responses. - [ ] Debug mode, stack traces, and directory listings are off in production. - [ ] Version banners are removed. - [ ] Authenticated responses send Cache-Control: no-store. - [ ] Authorization denials, auth failures, and admin actions are logged with a request id and no secrets, and something alerts on a spike.

Automate what a tool can find

Tooling catches the mechanical classes and frees review for logic. None of it detects broken access control, which is why the manual checklist above still matters.

LayerPurposeExamples
SASTPattern and dataflow bugs in your codeSemgrep, CodeQL, Bandit, gosec, Brakeman
SCAKnown CVEs in dependenciesnpm audit, pip-audit, Dependabot, Trivy, osv-scanner
Secret scanningCredentials in history and in diffsgitleaks, trufflehog, provider push protection
Container and IaCBase image CVEs, bad cloud configTrivy, Grype, Checkov, tfsec
DASTRuntime behavior against a running appOWASP ZAP, Nuclei
Header checkResponse header posturecurl -I, Mozilla Observatory
FuzzingParser and boundary robustnesslibFuzzer, go-fuzz, Atheris, Jazzer
# Fast local pass, all read-only
semgrep --config=p/owasp-top-ten .
gitleaks detect --source . --redact
npm audit --omit=dev
osv-scanner scan source -r .
trivy fs --scanners vuln,secret,misconfig .
curl -sI https://staging.example.com | grep -iE 'content-security|strict-transport'

Wire the fast ones into pre-commit and all of them into CI. Fail the build on new high-severity findings rather than on the total count, so an existing backlog does not train everyone to ignore the gate.

Write security tests, not just scans

A scanner tells you about a class of bug once. A test keeps it fixed. These are the highest-value assertions to add, in rough order of return.

def test_cross_tenant_read_is_404(client, user_b, invoice_of_org_a):
    client.login(user_b)
    r = client.get(f"/api/invoices/{invoice_of_org_a.id}")
    assert r.status_code == 404          # not 403, which would confirm existence

def test_mutation_without_csrf_token_is_rejected(client, user):
    client.login(user)
    r = client.post("/account/email", data={"email": "x@example.com"})
    assert r.status_code == 403

def test_role_is_not_mass_assignable(client, user):
    client.login(user)
    client.patch("/api/me", json={"name": "ada", "role": "admin"})
    assert reload(user).role == "member"

def test_session_id_changes_on_login(client, user):
    before = client.cookie("session")
    client.login(user)
    assert client.cookie("session") != before

def test_security_headers_present_on_error(client):
    r = client.get("/does-not-exist")
    assert r.headers["X-Content-Type-Options"] == "nosniff"

Every fixed vulnerability gets a regression test in the same commit. That is the single habit that keeps a codebase from re-introducing the same bug next quarter.

Practice legally

If you want to build hands-on skill, use environments that exist for it. These are intentionally vulnerable applications you run yourself or platforms that grant permission explicitly.

- OWASP Juice Shop, a modern intentionally vulnerable web app you self-host. - OWASP WebGoat, lesson-based and self-hosted. - PortSwigger Web Security Academy, free labs with written explanations of each class. - OWASP ASVS as the requirements checklist to verify a real application against. - HackerOne and Bugcrowd for programs where a company has published a scope and granted permission in writing.

Read the program's scope and rules before you touch anything, and report what you find through the channel it names.