The first item on an application security checklist is a second account, not a scanner: sign in as one customer and try to open another’s records. In 7 of the 21 third-party apps I audited, a confirmed authorization failure meant a logged-in user could read or write another customer’s data. Twelve checks follow, each with its test.

Those 21 apps are the ones I audited in June and July 2026, a selected set of audited apps and not a random sample, so the count tells you which check to run first, not how often AI-built apps fail it; the counts rest on human-verified findings. The list below is the testing layer of web app security for a single product: each check names the test that proves it, the result a correct build returns, and the evidence worth keeping.

The application security checklist

An application security checklist for one web app has 12 checks: tenant data isolated, every route authorized on the server, no private key in the browser or repository, hostile input rejected, the costly endpoint rate-limited, security headers sent, cross-origin access restricted, uploads bounded, vulnerable dependencies fixed, security events logged cleanly, the AI feature tested, a disclosure contact published.

It works as a web app security checklist for one product and as a security checklist for web application teams shipping a single codebase. Run the checks in this order, since the first three decide whether one customer can reach another’s data:

  • One customer cannot read or change another tenant’s records.
  • Every protected route and server action checks the caller’s role on the server.
  • No private key sits in the browser bundle, the network traffic or the repository history.
  • The server rejects input of the wrong type, the wrong length, or with fields it did not expect.
  • The most expensive endpoint has a per-account limit.
  • Production responses carry the security headers, and the app still works with them on.
  • Cross-origin reads and cross-site state changes are refused.
  • Uploads are checked for type and size, and stored files stay private.
  • Dependencies with known vulnerabilities are fixed, and the app still passes its tests.
  • Sign-ins and refused requests are logged, with no password, token or key in the line.
  • The AI feature validates model output before it touches data and stops at a per-user spend limit.
  • A security.txt file names a contact that actually receives reports.

Read down the first column of the table and you have an application security controls checklist; read across a row and you have the proof for one control. The test and expected-result cells are my method, and together they make a security testing checklist for the web app itself, run against the deployed application rather than the code on your laptop.

CheckThe testExpected resultEvidence to keepGoes deeper
1. Tenant data isolatedTwo ordinary accounts in two tenants; from one, request the other’s record ids through the app’s own APIRefused or emptyThe two requests and responses, datedmulti-tenant data isolation
2. Protected actions authorized on the serverCall each admin or owner-only route directly as an ordinary user, then signed outRefused (401, 403, 404 or a redirect to sign-in) and nothing changed; under Supabase row-level security, an empty result with the targeted row read back intactThe route list with the status each returnedbroken access control
3. Private keys kept out of the browser and repoSearch the built JavaScript, the Chrome DevTools Network panel and the repository history for your providers’ key prefixes; decode every JWT found and read its roleOnly publishable keysThe search terms and the empty resultswhich keys can live in the browser
4. Hostile input rejected on the serverSend each write endpoint wrong types, over-long strings and unexpected fields, bypassing the formA 4xx, or the unexpected fields dropped, and nothing invalid storedThe request set and the responsesinput validation for a web app
5. Costliest endpoint rate-limitedA burst of requests from one account, then ordinary useRefused (a 429, for example) past the limit, normal service below itThe count at which refusals beganrate limiting in an API
6. Security headers sent in productionRead the production response headers with curl -I, then click through the appThe headers present and no broken flowThe header dumpcontent security policy without unsafe-inline
7. Cross-origin access restrictedA credentialed request with an origin you never approved; a state-changing form posted from another origin while signed inThe response does not name that origin in Access-Control-Allow-Origin; the change does not happenBoth responsesCSRF mitigation
8. Uploads typed, sized and privateA disallowed type, an oversized file, a stored file’s plain URL opened signed outAll three refusedThe three responsesfile upload testing
9. Vulnerable dependencies fixednpm audit (by default it needs a lockfile) or the stack’s equivalent, then the app’s own testsNo unresolved high or critical advisory the app reaches; pip-audit’s README output shows no severity, so each advisory it lists is read against the appThe audit output, the fix, the test runthe npm audit command
10. Security events logged without secretsOne sign-in with a test password you chose, one refused request; read the log lines they madeBoth events logged; the test password, tokens and keys absentThe two log lineshow to prevent insufficient logging and monitoring
11. The AI feature testedInstruction-override inputs you wrote yourself; a spend test against the per-user limitModel output checked before it touches data; the limit stops the spendThe inputs, the outputs, the limit hitwhat prompt injection is
12. Disclosure contact publishedFetch /.well-known/security.txt and send a test message to its ContactThe file parses and the message arrivesThe file and the received messagea security.txt file example

Two rows lean on a source. Check 2’s empty result comes from how Supabase describes a policy: “Think of a policy as adding a WHERE clause to every query”, and because “Matching no rows is not proof on its own”, the test reads the targeted row back afterward. For check 4, OWASP’s Input Validation Cheat Sheet says input validation “must be implemented on the server-side before any data is processed by an application’s functions”, and that “Allowlist validation is appropriate for all input fields provided by the user.”

To use this as a security checklist template in Excel, paste the table into a sheet and add a date column; there is no download. Saved as an xls file, the same sheet is the web application security checklist a customer’s reviewer can open without a browser.

Each check has a matching deliverable in the Production Hardening Sprint (check 3 has two; the mapping is mine). The sprint verifies 1.4 by running read and write tests as anonymous users, different roles, and separate tenants ; 1.3 by calling protected actions directly as unauthorized and underprivileged users and confirming rejection ; 2.1 by scanning source and built assets and inspecting browser traffic for private credentials , with 2.3 by checking runtime configuration, built assets, and each code path using administrative credentials ; 3.1 by submitting invalid and hostile payloads and confirming safe rejection or handling ; 3.6 by simulating bursts and verifying enforcement without blocking ordinary use ; 3.4 by inspecting response headers and exercising the application to confirm intended protections without broken flows ; 3.3 by testing approved and unapproved origins, credential handling, and state-changing request protections ; 3.2 by testing disallowed formats, oversized files, unauthorized downloads, and storage listings ; 3.5 by recording the dependency scan, remediation, and regression checks after changes ; 8.1 by tracing a test request across services and checking log content for sensitive fields ; 3.11 by running injection test cases against each feature, a spend simulation that hits the limit, and a provider-outage simulation in staging ; and 3.9 by checking the published file, contact details, and delivery of a test message.

How to fill it in

This is a manual security testing checklist: a person runs it, in the order above, against the deployed app. A frontend security checklist before launch covers checks 3, 6 and 7 of it and stops there, since those three can be seen from the browser; the other nine need the server or the data. Keep your code security checklist notes in the ledger’s result column, one line per check. Read as fixes instead of tests, the same twelve lines make a web application hardening checklist. A scanner can run checks 6 and 9 and part of 3; the rest need a person, and checks 1 and 2 need two accounts (my reading).

Identity: checks 1 and 2

Testing as the admin teaches you nothing here, because an admin can open everything and every request succeeds. The server side permission checks checklist is short to write: list every route and server action, then call each one directly as the wrong role, and once more signed out. For check 1, sign in to the first tenant’s ordinary account, take a record id that belongs to the second tenant, and ask for it through the app’s own API. The two-account method, step by step, is in the API security checklist. File the requests, the responses and the date.

Secrets: check 3

Searching the source and skipping the built bundle is where this check goes wrong: the bundle is what the browser downloads, with every environment value the build baked in. Open the deployed app in Chrome, press Command+Option+J (Control+Shift+J on Windows and Linux), click the Network tab and reload. Then use the Search tab, which Chrome describes as the place “to search the HTTP headers and responses of all resources for a certain string or regular expression.” Search there for your providers’ secret-key prefixes, and run the same search over the repository’s history. On Supabase, the legacy anon and service_role keys are both a “JWT (long-lived)”, one “Legacy version of publishable keys” and the other “Legacy version of secret keys”, so decode each JWT you find and read its role; a prefix search alone cannot tell the public one from the secret one (my reading). File the search terms and the empty results. The builder-side fix is a set of prompts that change what the builder writes.

Input and abuse: checks 4 and 5

Validation that lives only in the form protects nothing on the server, because the endpoint behind the form sees whatever request arrives. Replay each write request outside the form, with curl or an API client, and change one thing at a time: a number where text belongs, a string far past any sensible length, a field the form never sends. Then check the table for anything that got stored. For check 5, send a quick burst from one signed-in account at the endpoint that costs you the most to serve, then use the app normally and confirm it still answers. File the request set, the responses and the count at which refusals began. The API security checklist above covers the API-shaped version of both checks.

The browser boundary: checks 6, 7 and 8

A content security policy that breaks the app gets switched off in a hurry, and then the headers check fails quietly. Read the headers from a real production response with curl -I against your live domain, look for a content security policy, HSTS and a framing rule, then click through sign-in, payment and any upload with them on. For check 7, send a credentialed request carrying an origin you never approved: the response must not name it in Access-Control-Allow-Origin, since a credentialed response needs an explicit origin and, without the credentials header, “the response is ignored by the browser and not returned to web content.” Then post a state-changing form from a page on another origin while signed in, and confirm nothing changed. For check 8, a file in a Supabase private bucket is “not accessible via a public URL”, while a signed URL is “time-limited” and works until it expires, so open the plain path signed out, not a signed link the app handed you. Before that, upload one file of a type the app should refuse and one far over its size limit, and confirm both are rejected with nothing stored.

In one app I audited, a medical app’s API, whenever its environment setting said development (the default when unset), allowed requests from any website and allowed credentials, with every method and header. The report notes the impact was limited because the token lived in browser storage, not a cookie, and that adding any cookie session would make it a cross-site data leak. The lesson I take from it: a cross-origin rule is only as strict as the environment the server thinks it is in, which is why the cross-origin check reads the headers production actually sends.

Supply chain: check 9

Running npm audit fix and shipping without a test run swaps one risk for another. npm describes the command as one that “submits a description of the dependencies configured in your project to your default registry and asks for a report of known vulnerabilities”, and its audit-level setting accepts the severities info, low, moderate, high and critical. “By default npm requires a package-lock or shrinkwrap in order to run the audit.” With fix, “remediations will be applied to the package tree”; add --force and it may “install modules outside your stated dependency range (including SemVer-major changes)”, which is why the regression run follows every fix. On Python, pip-audit’s README output shows name, version, ID and fix versions, with no severity column. File the audit output, the change and the test run.

Visibility: check 10

Logging the whole request body puts the password from every sign-in into your logs. Sign in once with a test password you chose for this, send one request the app should refuse, then read the log lines the two made. Search the logs for that test password and for your session token: finding both events and neither secret is the pass. A search for a value nobody typed proves nothing, which is why the password has to be one you entered. File the two log lines.

The AI feature and disclosure: checks 11 and 12

An AI feature with no per-user spend ceiling lets one account run up the bill. Write a few inputs of your own that tell the model to ignore its instructions, and confirm the output is validated before it writes anything; keep the tests on your own app, with your own inputs, never attack strings copied from elsewhere. Then drive one test account to the app’s own per-user limit and confirm the next call is refused. For check 12, RFC 9116 puts the file under /.well-known/ over HTTPS and requires two fields: Contact, “a method that researchers should use for reporting security vulnerabilities”, and Expires, which it recommends keeping “less than a year into the future to avoid staleness.” Fetch the file, send a test message to its Contact, and file the message you received.

The same list in other vocabularies: OWASP, NIST and Django

Other checklists cover the same ground in other words. OWASP’s Web Security Testing Guide is a testing resource for web application developers and security professionals, NIST SP 800-53 catalogs security and privacy controls for information systems and organizations, and Django’s deployment checklist covers one framework. An audit checklist is this list with a date, a name and a signature.

For a web application security checklist, OWASP’s own answer is the OWASP Web Security Testing Guide, and its project page offers v4.2 “as a web-hosted release and PDF” while stating “We are currently developing release version 5.0.” The checklist spreadsheet is not on that page. It sits in OWASP’s own wstg repository on GitHub, in a checklists folder that holds Excel, Markdown and JSON versions, and the folder’s README says “The current (Excel) checklist is based on v4.2 of the OWASP Testing Guide, as content for other versions is still under development.” That file, the OWASP testing checklist XLSX, is the one I would start from before any other GitHub copy or OWASP Top 10 checklist. Testing each of the Top 10 risks is a separate job: how to test the OWASP Top 10 vulnerabilities, one category at a time.

A NIST SaaS security checklist points at NIST SP 800-53, “Security and Privacy Controls for Information Systems and Organizations”, which NIST describes as “a catalog of security and privacy controls for information systems and organizations.” Its controls are “implemented as part of an organization-wide process to manage risk”, so it is written for organizations and is not a per-app test list (my reading).

A Django checklist is Django’s deployment checklist, which opens with “The internet is a hostile environment.” and says some of its checks “can be automated” with manage.py check --deploy, run “against your production settings file”. In my mapping, its SECRET_KEY setting belongs to check 3, CSRF_COOKIE_SECURE and SESSION_COOKIE_SECURE to checks 6 and 7, and LOGGING to check 10.

Website security standards, for one web app, come down to these bodies: OWASP for how to test, NIST for which controls an organization keeps. Add a date, a name and a signature column and the list becomes a website security audit checklist that a reviewer of the web application can sign; print that page to PDF and it is your security audit checklist PDF. An application security assessment checklist is the same list run by someone outside your team, which is a web application security audit.

A filled example

The app here is constructed, not a real one. Its assumptions: Next.js on Vercel, Supabase with row-level security, Stripe Checkout, one AI feature, built with a builder tool. The middle column shows what a filled ledger row looks like; it is never a claimed result for a real app. The right-hand column holds what my audits measured, where they measured that check.

CheckLedger row for the example app (test run, result, evidence)What my audits measured
1. Tenant data isolatedSecond tenant’s account asked the Supabase API for the first tenant’s project ids; empty result, the first tenant’s rows read back intact; both request and response pairs saved7 of the 21 third-party apps: the cross-user or cross-tenant failures described under the opener
2. Protected actions authorizedEvery admin route handler called as an ordinary user and signed out; each refused or redirected to sign-in; route list with statuses saved11 of the 21 third-party apps had unauthenticated endpoints doing privileged work
3. No private key in the browserNetwork panel search for the Stripe and Supabase secret-key prefixes found nothing; the JWT in the bundle decoded to the anon role; search terms saved6 of the 21 third-party apps shipped a real secret
4. Hostile input rejectedProject-create endpoint sent a number as the name, an over-long name and an extra owner field; the first two rejected with a validation error, the extra field dropped, nothing storedNot measured in my audits
5. Costliest endpoint rate-limitedBurst at the AI endpoint from one account; refusals began at the limit set in code; normal use worked afterward13 of the 21 third-party apps had no rate limiting on their most expensive endpoint
6. Security headers sentcurl -I on the production domain showed the content security policy, HSTS and framing rule; sign-in, checkout and upload clicked throughNot measured in my audits
7. Cross-origin access restrictedCredentialed request from an unapproved origin got no Access-Control-Allow-Origin for it; a form posted from another origin changed nothingNot measured in my audits
8. Uploads typed, sized and privateDisallowed type, oversized file and a private file’s plain URL opened signed out; all refusedNot measured in my audits
9. Vulnerable dependencies fixednpm audit on the lockfile, high advisories fixed, test suite rerun and passing; output savedNot measured in my audits
10. Events logged without secretsSign-in with a chosen test password and one refused request; both events logged, test password and token not foundNot measured in my audits
11. AI feature testedOwn instruction-override inputs; model output validated before any write; the test account refused at its per-user limit8 of the 14 AI apps had a live prompt-injection path, with untrusted text flowing straight into the model’s instructions
12. Disclosure contact published/.well-known/security.txt fetched with Contact and Expires present; test message receivedNot measured in my audits

The right-hand counts come from my June and July 2026 audits: 21 third-party apps, and 14 AI apps for check 11. They are a chosen group of audited apps rather than a sample of the market, so read each count as a reason to run that check early, never as a failure rate for your app or for AI-built apps at large. A 7 point security checklist for SaaS apps, in my cut, is checks 1 to 5, 9 and 10; the complete publish security checklist is all twelve.

How to verify the result

A check is done when its evidence row exists: the date, who ran it, the command or click path, the result, and a screenshot or log line. Twelve rows make a ledger a reviewer can read; a scanner report alone covers only the checks a scanner can run.

Ledger columnWhat goes in it
CheckThe check’s number and name from the list
DateThe day the test ran
Who ran itThe person, by name or role
Command or click pathThe exact command, request or DevTools path used
ResultWhat came back, in one line
Evidence fileThe saved response, log line or screenshot

A check without a row is not done, however sure anyone feels about it. Where the ledger sits among an app’s other documents is under keep a test map and an evidence ledger. What a builder’s own security scan covers, and what stays with you, is the subject of the vibe code security check. For an app built with an AI builder, the vibe coding security checklist orders its checks by when each risk arrives.

Where the sprint does this

Deliverable 13.1 of the Production Hardening Sprint, the production readiness report, is verified this way: account for all 123 IDs; keep failures visible until resolved and explain genuine non-applicable items. The twelve checks’ deliverables, listed in the paragraph after the checklist table, are among those IDs. Formal third-party certifications and independent audit opinions are separate from these engineering deliverables. Each ID, with its verify line, is listed on the published scope.

Common questions about security checklists for web apps

What is a security checklist?

A security checklist is a list of conditions an app has to meet, each paired with a test that proves it and the evidence that it passed. A list of controls with no test is a statement of intent; the test and the saved result are what let someone else check your work.

What are the top 10 web security checklists according to OWASP?

The OWASP Top 10 is not ten checklists: OWASP calls it “a standard awareness document for developers and web application security”, covering “the most critical security risks to web applications.” Two of OWASP’s checklist-shaped projects are the Web Security Testing Guide, whose Excel checklist is based on v4.2, and the Secure Coding Practices Quick-reference Guide, which OWASP says “has now been archived”, its checklists “migrated to the Developer Guide”.

Where can I find the OWASP Wstg checklist?

The OWASP WSTG checklist is in OWASP’s wstg repository on GitHub, in the checklists folder, as an Excel file based on v4.2 of the guide. The project page itself offers the guide as a web-hosted release and a PDF.

How to do security testing for web applications?

Run the twelve checks above in order against the deployed app, with two ordinary accounts for the first two, and write each result into the ledger. Then add a scanner for what a scanner can run: the headers, the dependencies and part of the key search.

How to make an audit checklist?

Start from a list of checks that each have a test, then add three columns: the date, the name of whoever ran it, and a signature. Fill one row per check as it runs, and keep the evidence file next to each row.