Quick Answer
A thorough website security checklist covers five layers: transport security (SSL/TLS, HTTPS, security headers), authentication and access control, code-level vulnerabilities (injection, XSS, CSRF), dependency and supply chain hygiene, and secrets management. This page covers all 50 checks with concrete pass/fail criteria and automated tooling for each.
Why You Need a Website Security Checklist in 2026
AI coding tools have changed how websites get built. In 2026, a solo founder can ship a production web app in a single afternoon. But the speed of AI-generated code does not include security by default. Apiiro's research found that AI-generated code contains security vulnerabilities at 2.74x the rate of human-written code. The functional requirements get satisfied; the security requirements get skipped.
A website security checklist exists to close that gap. It gives you a repeatable process to verify that every security control is in place - whether your app was written by a human, an AI, or both. This checklist covers everything VibeDoctor scans automatically, organized into five layers you can work through systematically.
| Layer | Checks | Typical issues in AI-generated apps |
|---|---|---|
| 1. Transport Security | 10 | No HSTS, missing CSP, self-signed cert |
| 2. Authentication & Access Control | 10 | Unprotected routes, insecure cookies, no rate limiting |
| 3. Code Vulnerabilities | 14 | SQL injection, XSS, CORS wildcard, CSRF, eval() |
| 4. Dependencies & Supply Chain | 8 | Known CVEs, loose version pinning, no lockfile |
| 5. Secrets & Configuration | 8 | Committed .env, hardcoded API keys, exposed NEXT_PUBLIC secrets |
Layer 1: Transport Security (10 Checks)
1.1 HTTPS Enforced on All Pages
Pass: All HTTP traffic redirects to HTTPS with a 301 permanent redirect. No mixed content (HTTP resources loaded on HTTPS pages).
How to check: Load your site over http:// and verify it redirects. Check your Caddy/nginx/Vercel config for the redirect rule.
1.2 SSL Certificate Valid and Not Expiring
Pass: Certificate is issued by a trusted CA, valid for the current domain, and has more than 14 days until expiry. Protocol is TLS 1.2 or 1.3 (not SSLv3 or TLS 1.0).
How to check: Run openssl s_client -connect yourdomain.com:443 or use SSL Labs' free checker.
1.3 HSTS Header Present
Pass: Response includes Strict-Transport-Security: max-age=31536000; includeSubDomains.
Why it matters: Without HSTS, a user who typed your URL over HTTP before being redirected is vulnerable to SSL stripping attacks.
1.4 Content Security Policy (CSP) Header
Pass: Content-Security-Policy header is present and restricts script sources. At minimum, avoid unsafe-inline and unsafe-eval.
Caution: A missing or overly permissive CSP is the second most common security header issue in vibe-coded apps after missing HSTS.
1.5 X-Frame-Options or frame-ancestors CSP Directive
Pass: Response includes X-Frame-Options: DENY or SAMEORIGIN, or CSP frame-ancestors 'none'. Prevents your app from being embedded in iframes for clickjacking attacks.
1.6 X-Content-Type-Options Header
Pass: X-Content-Type-Options: nosniff is present. Prevents browsers from MIME-sniffing responses away from the declared content type.
1.7 Referrer-Policy Header
Pass: Referrer-Policy: strict-origin-when-cross-origin or stricter. Prevents sensitive URL paths from leaking to third-party sites via the Referer header.
1.8 Permissions-Policy Header
Pass: Permissions-Policy header restricts access to browser features like camera, microphone, and geolocation that your app does not need.
1.9 No Server Version Disclosure
Pass: Response headers do not include Server: nginx/1.24.0 or X-Powered-By: Express. Version information helps attackers target known exploits.
1.10 No Mixed Content
Pass: All resources (images, scripts, stylesheets, fonts, API calls) are loaded over HTTPS. No http:// URLs in your HTML or JavaScript.
Layer 2: Authentication and Access Control (10 Checks)
2.1 All API Routes Require Authentication
Pass: Every API endpoint that returns or modifies user data requires a valid session or token. Routes that should be public are explicitly whitelisted.
This is the #1 issue in AI-generated apps. AI tools generate working endpoints but almost never add authentication middleware by default.
// ❌ FAIL - No auth, any visitor can call this
app.get('/api/users', async (req, res) => {
const users = await db.query('SELECT * FROM users');
res.json(users);
});
// ✅ PASS - Auth required
app.get('/api/users', requireAuth, async (req, res) => {
const users = await db.query('SELECT * FROM users');
res.json(users);
});
2.2 Session Cookies Use httpOnly, Secure, and SameSite Flags
Pass: All session or auth cookies are set with httpOnly=true (prevents JS access), Secure=true (HTTPS only), and SameSite=Strict or Lax (CSRF protection).
2.3 Rate Limiting on Auth Endpoints
Pass: Login, registration, password reset, and magic link endpoints have rate limiting (e.g. max 10 attempts per 15 minutes per IP). Prevents brute force and credential stuffing attacks.
2.4 Password or Token Requirements
Pass: If using passwords, minimum 8 characters enforced server-side. If using JWTs: strong algorithm (RS256 or ES256), short expiry (<1 hour access tokens), rotation implemented.
2.5 Admin Routes Require Separate Authorization
Pass: Admin endpoints check both authentication (is this a valid user?) and authorization (is this user an admin?). Being authenticated is not enough to access admin functionality.
2.6 No Insecure Direct Object References (IDOR)
Pass: When loading user-specific data by ID (e.g. /api/reports/123), the server verifies the requesting user owns resource 123. Never trust the client-supplied ID alone.
2.7 CSRF Protection on State-Changing Requests
Pass: State-changing operations (create, update, delete) use POST/PUT/DELETE methods, not GET. CSRF tokens or SameSite cookie policy are used to prevent cross-site request forgery.
2.8 Secure Password Reset Flow
Pass: Reset tokens are single-use, time-limited (<1 hour), cryptographically random (not Math.random()), and invalidated after use or on password change.
2.9 No Cryptographically Weak Token Generation
Pass: Tokens, IDs, and nonces use crypto.randomUUID() or crypto.randomBytes(), never Math.random() or Date.now().
2.10 Account Lockout or Progressive Delays
Pass: After N failed login attempts, the account is temporarily locked or progressive delays are applied. Prevents automated credential stuffing.
Layer 3: Code Vulnerabilities (14 Checks)
3.1 No SQL Injection via String Interpolation
Pass: All database queries use parameterized queries or prepared statements. No template literals, string concatenation, or $queryRaw with user input.
// ❌ FAIL - SQL injection
const user = await db.query(`SELECT * FROM users WHERE email = '${req.body.email}'`);
// ✅ PASS - Parameterized
const user = await db.query('SELECT * FROM users WHERE email = $1', [req.body.email]);
3.2 No XSS via Unescaped HTML Rendering
Pass: No dangerouslySetInnerHTML, .innerHTML = userContent, v-html, or {@html} with unsanitized user data. All user-supplied content is escaped before display.
3.3 CORS Not Configured as Wildcard
Pass: Access-Control-Allow-Origin is set to a specific domain list, not * for credentialed requests. Wildcard CORS with credentials is a critical misconfiguration.
3.4 Input Validation on All API Endpoints
Pass: Request body and query parameters are validated against a schema (Zod, Joi, etc.) before processing. Unexpected types cause a 400 error, not a 500 crash or silent data corruption.
3.5 No eval() or new Function() with User Input
Pass: No dynamic code execution. eval(), new Function(), and setTimeout(string) are absent from the codebase, or never receive user-controlled content.
3.6 File Uploads Are Validated
Pass: Upload endpoints validate MIME type from magic bytes (not filename), enforce file size limits, sanitize filenames, and store files outside the web root or in object storage.
3.7 Secrets Not Exposed to the Client
Pass: No NEXT_PUBLIC_, VITE_, or similar browser-exposed env vars contain API keys, private tokens, or database credentials. Only truly public config (feature flags, public API URLs) uses browser-exposed prefixes.
3.8 No Path Traversal in File Operations
Pass: When serving files based on user-provided paths or filenames, paths are resolved and verified to stay within the intended directory. path.normalize() is used and checked against the base path.
3.9 Third-Party Script Integrity
Pass: Third-party scripts loaded from CDNs use Subresource Integrity (SRI) hashes. A compromised CDN cannot serve malicious scripts to your users.
3.10 No Sensitive Data in URLs
Pass: Session tokens, passwords, and API keys never appear in query strings. URL parameters are logged by servers, proxies, and browser history.
3.11 Error Messages Do Not Expose Internals
Pass: Production error responses return generic messages to clients ("Something went wrong"). Stack traces, database query details, and file paths are logged server-side only.
3.12 WebSocket Connections Are Authenticated
Pass: WebSocket upgrade requests verify the client's session or token before establishing the connection. The origin is validated against an allowlist.
3.13 No Server-Side Request Forgery (SSRF) Vectors
Pass: If your app fetches URLs provided by users (e.g. webhook endpoints, link previews), the destination is validated against an allowlist or blocked from accessing internal IP ranges (169.254.x.x, 10.x.x.x, 172.16.x.x, 192.168.x.x).
3.14 Logging Does Not Include Secrets or PII
Pass: Application logs do not contain passwords, API keys, full credit card numbers, or other sensitive data. Log output is sanitized or uses structured logging with field-level redaction.
Layer 4: Dependencies and Supply Chain (8 Checks)
4.1 No Known CVEs in Direct Dependencies
Pass: Running npm audit or Trivy shows no critical or high severity CVEs in production dependencies. Dev dependencies with CVEs are acceptable if never bundled into production.
4.2 No Known CVEs in Transitive Dependencies
Pass: The full dependency tree (not just direct dependencies) is checked. Trivy and npm audit --all cover transitive dependencies that npm audit alone may miss.
4.3 Package Versions Are Pinned
Pass: package.json does not use *, latest, or >= version ranges for production dependencies. Pin to exact versions ("express": "4.18.2") or at minimum use ^ with a lockfile.
4.4 Lockfile Committed to Repository
Pass: package-lock.json, yarn.lock, or pnpm-lock.yaml is committed and up to date. Builds without a lockfile can silently pull newer (potentially vulnerable) versions.
4.5 Only One Package Manager Used
Pass: The repository contains a lockfile for exactly one package manager. Having both package-lock.json and yarn.lock creates inconsistent installs across environments.
4.6 No Excessively Broad Package Count
Pass: Production dependencies are reviewed for necessity. Unused or redundant packages increase attack surface. Target fewer than 40 direct production dependencies for typical apps.
4.7 Docker Base Images Are Pinned
Pass: Dockerfile uses pinned image digests or specific version tags (e.g. FROM node:20.11-alpine), not :latest. Unpinned images make builds non-reproducible.
4.8 Dependencies Scanned on Every Push
Pass: CI/CD pipeline runs npm audit or equivalent on every commit. New vulnerabilities in existing dependencies are caught immediately, not in a monthly review.
Layer 5: Secrets and Configuration (8 Checks)
5.1 No .env File Committed to Git
Pass: .env does not exist in the repository and is listed in .gitignore. A committed .env exposes all secrets to everyone with repository access and persists in git history even after deletion.
5.2 No Secrets in Git History
Pass: Gitleaks or similar tool has been run across the entire git history - not just the current file tree. Secrets committed in the past and "deleted" are still present in git history.
5.3 No Hardcoded API Keys in Source Code
Pass: Search the codebase for patterns like sk_live_, sk-, AKIA, Bearer , and known secret prefixes. None appear as string literals in source files.
5.4 .env.example Committed and Up to Date
Pass: .env.example (or .env.sample) exists with placeholder values for every required env var. New developers can get the app running without hunting for undocumented configuration.
5.5 Production Secrets Rotated If Previously Exposed
Pass: If any secret was ever committed, leaked, or shared insecurely, it has been rotated in every service it was used. "Deleting" a secret from code does not revoke access for anyone who saw the history.
5.6 Secrets Are Stored in Environment Variables, Not Config Files
Pass: Database URLs, API keys, and tokens are read from process.env at runtime, not hardcoded in configuration files that might be checked in or logged.
5.7 Debug Mode Disabled in Production
Pass: NODE_ENV=production is set. Debug flags, verbose logging, and development tools (like React DevTools) are disabled. DEBUG=* is not set in production environment variables.
5.8 Principle of Least Privilege for Service Keys
Pass: Database users, Supabase service role keys, and AWS IAM roles are scoped to the minimum permissions required. The app does not use admin credentials for routine reads and writes.
Using This Checklist Automatically
Working through 50 checks manually before every release is time-consuming. VibeDoctor automates the majority of this checklist - SSL validation, all 6 security headers, secret detection via Gitleaks, dependency CVEs via Trivy, and 30+ code vulnerability patterns via static analysis.
For checks that require manual review (IDOR verification, auth flow design, secret rotation policy), use the automated scan output as a starting point - it surfaces the technical issues so your manual review can focus on the design decisions.
Run a free automated security scan - paste your URL or connect your GitHub repo and get a report in about 2 minutes covering the majority of these 50 checks.
Website Security Checklist: Printable Summary
| # | Check | Layer | Automated? |
|---|---|---|---|
| 1 | HTTPS enforced, no mixed content | Transport | Yes |
| 2 | SSL valid, TLS 1.2+, >14 days to expiry | Transport | Yes |
| 3 | HSTS header present | Transport | Yes |
| 4 | Content-Security-Policy header | Transport | Yes |
| 5 | X-Frame-Options / frame-ancestors | Transport | Yes |
| 6 | X-Content-Type-Options: nosniff | Transport | Yes |
| 7 | Referrer-Policy header | Transport | Yes |
| 8 | Permissions-Policy header | Transport | Yes |
| 9 | No server version disclosure | Transport | Yes |
| 10 | No mixed content (HTTP on HTTPS) | Transport | Yes |
| 11 | All API routes require auth | Auth | Partial |
| 12 | httpOnly, Secure, SameSite cookies | Auth | Yes |
| 13 | Rate limiting on auth endpoints | Auth | Partial |
| 14 | Strong password/token requirements | Auth | Partial |
| 15 | Admin routes separately authorized | Auth | Manual |
| 16 | No IDOR (ownership verified by server) | Auth | Manual |
| 17 | CSRF protection on state-changing routes | Auth | Partial |
| 18 | Secure password reset flow | Auth | Manual |
| 19 | Cryptographically random tokens | Auth | Yes |
| 20 | Account lockout after failed attempts | Auth | Manual |
| 21 | No SQL injection | Code | Yes |
| 22 | No XSS via unescaped HTML | Code | Yes |
| 23 | No wildcard CORS | Code | Yes |
| 24 | Input validation on all endpoints | Code | Yes |
| 25 | No eval() with user input | Code | Yes |
| 26 | File uploads validated (type, size, path) | Code | Yes |
| 27 | No secrets in client-side code | Code | Yes |
| 28 | No path traversal in file operations | Code | Yes |
| 29 | Third-party scripts use SRI hashes | Code | Manual |
| 30 | No sensitive data in URLs | Code | Manual |
| 31 | Generic error messages to clients | Code | Partial |
| 32 | WebSocket connections authenticated | Code | Partial |
| 33 | No SSRF vectors | Code | Partial |
| 34 | Logs do not include secrets or PII | Code | Partial |
| 35 | No critical CVEs in direct deps | Deps | Yes |
| 36 | No critical CVEs in transitive deps | Deps | Yes |
| 37 | Package versions pinned | Deps | Yes |
| 38 | Lockfile committed | Deps | Yes |
| 39 | Single package manager | Deps | Yes |
| 40 | Reasonable dependency count | Deps | Yes |
| 41 | Docker images pinned (not :latest) | Deps | Yes |
| 42 | Deps scanned in CI/CD | Deps | Manual |
| 43 | No .env in git | Secrets | Yes |
| 44 | No secrets in git history | Secrets | Yes |
| 45 | No hardcoded API keys in source | Secrets | Yes |
| 46 | .env.example committed | Secrets | Yes |
| 47 | Exposed secrets rotated | Secrets | Manual |
| 48 | Secrets in env vars, not config files | Secrets | Yes |
| 49 | Debug mode disabled in production | Secrets | Yes |
| 50 | Least privilege service credentials | Secrets | Manual |
FAQ
What is a website security checklist?
A website security checklist is a structured list of controls to verify before launching or maintaining a web application. It ensures transport security, secure authentication, code-level vulnerability prevention, dependency hygiene, and proper secrets management are all in place.
How often should I run a website security audit?
Run automated scans on every code push and before every release. Perform a manual review of auth flows, access control, and secrets management at least quarterly. Dependency CVE scanning should be continuous - new vulnerabilities are published daily.
Can I automate website security checks?
Yes - about 70% of this checklist can be automated. SSL validation, all security headers, Gitleaks secret detection, Trivy CVE scanning, and 30+ code pattern checks run automatically. The remaining 30% - primarily auth design decisions and access control review - require a manual walk-through.
What is the most commonly missed item on a web security checklist?
For AI-generated apps, the most commonly missed items are: (1) missing authentication on API routes, (2) no security headers at all, and (3) hardcoded API keys or a committed .env file. These three account for the majority of critical findings in VibeDoctor scans.
Is this checklist the same as an OWASP audit?
This checklist covers the OWASP Top 10 and more, but is structured for web application developers rather than security auditors. It focuses on actionable pass/fail criteria rather than theoretical threat modeling. For a detailed mapping to OWASP, see OWASP Top 10 for Vibe Coders.
How long does a manual security review take?
For a small app (under 20 endpoints), a manual review of the checks marked "Manual" in the table above takes 2-4 hours. Automated scanning covers most of the time-consuming technical checks, leaving the manual effort focused on design review and business logic verification.