The OWASP Top 10 is the industry-standard reference for the most critical web application security risks. Whether you're building a SaaS product, an e-commerce site, or an internal tool, these ten categories cover the vulnerabilities most likely to get you breached.
Here's a practical checklist you can use to audit your application against each one, complete with code examples, severity ratings, quick tests, and real-world breach references.
A01: Broken Access Control
Severity: Critical | Prevalence: #1 most common | Quick test: Change an ID in the URL and see if you can access another user's data
Access control failures are the number one web application security risk. They happen when users can act outside their intended permissions. In the 2021 OWASP analysis, 94% of applications tested had some form of broken access control, with a 3.81% incidence rate across 318,000+ data points.
Real-world breach: In 2019, a major US financial institution (Capital One) suffered a breach exposing 106 million customer records due to a misconfigured WAF combined with SSRF — an access control failure that allowed an attacker to access internal metadata services. More recently, in 2024, multiple SaaS applications were found to have Insecure Direct Object Reference (IDOR) vulnerabilities that allowed users to access other users' billing data simply by changing a numeric ID in API requests.
What to check:
- Can users access other users' data by changing an ID in the URL?
- Are API endpoints checking authentication AND authorization?
- Can users escalate their role or permissions?
- Are admin functions accessible to regular users?
- Do you enforce server-side access control (not just hiding UI elements)?
Code example — vulnerable vs. secure:
// VULNERABLE: No authorization check — any authenticated user can access any user's data
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
const profile = await db.query('SELECT * FROM profiles WHERE user_id = $1', [req.params.id]);
res.json(profile);
});
// SECURE: Verify the requesting user owns the resource
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
if (req.user.id !== req.params.id && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
const profile = await db.query('SELECT * FROM profiles WHERE user_id = $1', [req.params.id]);
res.json(profile);
});
In Supabase, the equivalent protection is Row Level Security (RLS):
-- RLS policy: users can only read their own profile
CREATE POLICY "Users can view own profile"
ON profiles FOR SELECT
USING (auth.uid() = user_id);
How CheckVibe helps: Our auth scanner tests for missing authentication on endpoints, IDOR patterns, and privilege escalation paths.
A02: Cryptographic Failures
Severity: High | Prevalence: #2 | Quick test: Check if your site uses HTTPS and test TLS version with curl -v
Previously called "Sensitive Data Exposure," this category covers failures in cryptography that lead to data exposure. This includes transmitting data in cleartext, using weak encryption algorithms, and improper key management.
Real-world breach: The 2017 Equifax breach (147 million records) was partially enabled by an expired TLS certificate on an internal inspection tool, which meant encrypted traffic containing stolen data was not being monitored. In 2023, researchers found that 25% of the top 100,000 websites still supported TLS 1.0 or 1.1, both of which have known vulnerabilities.
What to check:
- Is data transmitted over HTTPS with a valid certificate?
- Are TLS versions current (TLS 1.2+ only)?
- Are passwords hashed with strong algorithms (bcrypt, scrypt, argon2)?
- Are API keys and secrets stored securely (not in source code)?
- Is HSTS enabled to prevent protocol downgrade?
- Are sensitive cookies marked with the
Secureflag?
Code example — proper password hashing:
import bcrypt from 'bcrypt';
// SECURE: Hash passwords with bcrypt (cost factor 12)
const hashPassword = async (plaintext) => {
const saltRounds = 12;
return await bcrypt.hash(plaintext, saltRounds);
};
// SECURE: Compare passwords safely
const verifyPassword = async (plaintext, hash) => {
return await bcrypt.compare(plaintext, hash);
};
// INSECURE alternatives to avoid:
// - MD5, SHA1, SHA256 without salt (rainbow table attacks)
// - Storing passwords in plaintext
// - Using a static salt for all users
How CheckVibe helps: The SSL/TLS scanner checks certificate validity, protocol versions, and cipher strength. The API key scanner detects exposed secrets.
A03: Injection
Severity: Critical | Prevalence: #3 | Quick test: Enter ' OR 1=1 -- in a search field and see if it returns unexpected results
Injection flaws — SQL injection, XSS, command injection — occur when untrusted data is sent to an interpreter without validation. SQL injection alone accounts for roughly 33% of all web application vulnerabilities according to NIST's National Vulnerability Database.
Real-world breach: The 2008 Heartland Payment Systems breach (130 million credit card numbers) was caused by SQL injection. More recently, the 2023 MOVEit Transfer vulnerability (CVE-2023-34362) was a SQL injection flaw that led to data theft affecting over 2,500 organizations and 60+ million individuals.
What to check:
- Are all database queries parameterized?
- Is user input sanitized before rendering in HTML?
- Are command-line calls using safe APIs (not string concatenation)?
- Is there a Content Security Policy blocking inline scripts?
- Are ORM/query builders used instead of raw SQL?
Code example — SQL injection prevention:
// VULNERABLE: String concatenation — classic SQL injection
const search = async (query: string) => {
return await db.query(`SELECT * FROM products WHERE name LIKE '%${query}%'`);
// Attacker input: ' UNION SELECT username, password FROM users --
};
// SECURE: Parameterized query
const search = async (query: string) => {
return await db.query(
'SELECT * FROM products WHERE name LIKE $1',
[`%${query}%`]
);
};
Code example — XSS prevention in React:
// VULNERABLE: Renders raw HTML from user input
function Comment({ body }) {
return <div dangerouslySetInnerHTML={{ __html: body }} />;
}
// SECURE: Sanitize with DOMPurify before rendering
import DOMPurify from 'dompurify';
function Comment({ body }) {
const clean = DOMPurify.sanitize(body, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href'],
});
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
How CheckVibe helps: Dedicated SQL injection and XSS scanners test with hundreds of payload variations across all input vectors.
A04: Insecure Design
Severity: High | Prevalence: #4 | Quick test: Try to abuse a feature — can you create unlimited free trial accounts? Can you bypass payment?
This is about fundamental design flaws, not implementation bugs. No amount of code fixing can address a design that's insecure by nature. OWASP added this category in 2021 to emphasize that security must be considered from the design phase, not bolted on after implementation.
Real-world example: Many SaaS applications allow users to upgrade their plan via a client-side API call that trusts the plan name from the request body. An attacker changes "plan": "free" to "plan": "enterprise" and gains premium features. This is a design flaw — the pricing logic should be enforced server-side using validated Stripe webhook data, not client input.
What to check:
- Is there threat modeling for critical features?
- Are security requirements defined before development?
- Do business logic flows have abuse-case testing?
- Are rate limits in place for sensitive operations (login, password reset)?
- Is there defense in depth (multiple security layers)?
Code example — rate limiting a login endpoint:
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(5, '15 m'), // 5 attempts per 15 minutes
});
export async function POST(request: Request) {
const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
const { success } = await ratelimit.limit(`login:${ip}`);
if (!success) {
return new Response('Too many login attempts. Try again later.', {
status: 429,
});
}
// ... proceed with login logic
}
How CheckVibe helps: The DDoS/rate-limit scanner checks for rate limiting and WAF presence. The CSRF scanner verifies anti-forgery protections.
A05: Security Misconfiguration
Severity: Medium-High | Prevalence: #5 (most common finding in CheckVibe scans) | Quick test: Visit yourdomain.com and check response headers in browser DevTools
The most common issue we find in scans. Default configurations, unnecessary features, and missing hardening. OWASP reports a 4.51% incidence rate, with 90% of applications tested for misconfiguration having some form of it.
Real-world breach: The 2019 Facebook breach (540 million records exposed) was caused by third-party apps storing Facebook data on misconfigured Amazon S3 buckets with public access. Security misconfiguration is also the root cause of most cloud data leaks — Gartner estimates that through 2025, 99% of cloud security failures are the customer's fault (misconfiguration, not provider flaws).
What to check:
- Are security headers configured (CSP, HSTS, X-Frame-Options)?
- Is directory listing disabled?
- Are default credentials changed?
- Are error messages generic (not exposing stack traces)?
- Are unnecessary HTTP methods disabled?
- Are CORS policies restrictive?
Code example — Next.js security headers configuration:
// next.config.js — add security headers
const securityHeaders = [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://*.supabase.co;",
},
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
];
module.exports = {
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }];
},
};
How CheckVibe helps: The security headers scanner checks all major headers. The CORS scanner tests for overly permissive policies. The tech scanner identifies exposed version information.
A06: Vulnerable and Outdated Components
Severity: Medium-High | Prevalence: #6 | Quick test: Run npm audit in your project directory
Using components with known vulnerabilities is one of the easiest attack vectors. The Synopsys 2024 Open Source Security and Risk Analysis report found that 84% of codebases contained at least one known vulnerability, and 48% contained high-severity vulnerabilities.
Real-world breach: The 2017 Equifax breach was caused by a known vulnerability in Apache Struts (CVE-2017-5638) that had a patch available two months before the breach. The 2021 Log4Shell vulnerability (CVE-2021-44228) in Log4j affected hundreds of millions of systems and demonstrated how a single dependency vulnerability can cascade across the entire software supply chain.
What to check:
- Are all npm/pip/gem dependencies up to date?
- Are there known CVEs in your dependency tree?
- Is the framework version current and supported?
- Are unused dependencies removed?
- Do you have automated dependency scanning in CI/CD?
Code example — automated dependency auditing in CI/CD:
# .github/workflows/security.yml
name: Security Audit
on:
push:
branches: [main]
schedule:
- cron: '0 6 * * 1' # Weekly Monday 6am
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm audit --audit-level=high
continue-on-error: false # Fail the build on high-severity CVEs
How CheckVibe helps: The dependency scanner checks your package.json against known vulnerability databases and flags outdated packages.
A07: Identification and Authentication Failures
Severity: High | Prevalence: #7 | Quick test: Try logging in with common passwords, check if rate limiting kicks in, test session cookies for Secure/HttpOnly flags
Weaknesses in authentication mechanisms that allow attackers to compromise passwords, keys, or session tokens. Verizon's 2024 Data Breach Investigations Report found that stolen credentials were involved in 44% of breaches.
Real-world breach: The 2024 Snowflake-related breaches (affecting Ticketmaster, AT&T, and others) were caused by compromised customer credentials without multi-factor authentication. Attackers used credentials stolen from earlier breaches to access accounts that had no MFA requirement.
What to check:
- Is multi-factor authentication available?
- Are passwords enforced with minimum complexity?
- Are session tokens rotated after login?
- Is there brute force protection (account lockout, rate limiting)?
- Are session cookies marked Secure, HttpOnly, SameSite?
Code example — secure session cookie configuration:
// Setting secure session cookies in Next.js
import { cookies } from 'next/headers';
export async function setSessionCookie(token: string) {
const cookieStore = await cookies();
cookieStore.set('session', token, {
httpOnly: true, // Not accessible via JavaScript (prevents XSS theft)
secure: true, // Only sent over HTTPS
sameSite: 'lax', // Prevents CSRF from cross-origin requests
path: '/',
maxAge: 60 * 60 * 24, // 24 hours
});
}
// INSECURE alternatives to avoid:
// - localStorage.setItem('token', jwt) — readable by any XSS payload
// - Cookies without httpOnly — same XSS risk
// - Cookies without secure — sent over HTTP in cleartext
// - sameSite: 'none' — allows cross-origin requests (CSRF risk)
How CheckVibe helps: The auth scanner tests login flows, the cookie scanner checks session security flags, and the CSRF scanner verifies token implementation.
A08: Software and Data Integrity Failures
Severity: Medium-High | Prevalence: #8 | Quick test: Check if external scripts use Subresource Integrity (SRI) hashes, verify your CI/CD pipeline requires code review
Failures related to code and infrastructure that don't protect against integrity violations — like insecure CI/CD pipelines or auto-update mechanisms. This category was added in the 2021 OWASP update, largely in response to supply chain attacks like SolarWinds.
Real-world breach: The 2020 SolarWinds attack compromised the build pipeline to inject malicious code into a legitimate software update, affecting 18,000+ organizations including US government agencies. The 2021 Codecov incident compromised a bash uploader script, exposing environment variables (including credentials) from thousands of CI/CD pipelines. These incidents demonstrate why pipeline security and integrity verification are critical.
What to check:
- Are dependencies verified with lock files and checksums?
- Is the CI/CD pipeline secured against unauthorized changes?
- Are subresource integrity (SRI) hashes used for external scripts?
- Are code reviews required before merging?
Code example — Subresource Integrity for external scripts:
<!-- VULNERABLE: Loading external script without integrity check -->
<script src="https://cdn.example.com/analytics.js"></script>
<!-- SECURE: SRI hash ensures the script hasn't been tampered with -->
<script
src="https://cdn.example.com/analytics.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8w"
crossorigin="anonymous"
></script>
If an attacker compromises the CDN and modifies analytics.js, the browser will refuse to execute it because the hash will not match. Without SRI, any compromise of the CDN means arbitrary JavaScript execution on your site.
How CheckVibe helps: The security headers scanner checks for SRI usage and the GitHub security scanner audits your repository configuration.
A09: Security Logging and Monitoring Failures
Severity: Medium | Prevalence: #9 | Quick test: Trigger a failed login and check if it appears in your application logs
Without logging and monitoring, breaches go undetected. Most breaches are discovered by external parties, not the victim. IBM's 2024 Cost of a Data Breach Report found that the average time to identify and contain a breach was 258 days, and organizations with security AI and automation saved an average of $1.76 million per breach.
Real-world breach: The 2013 Target breach (40 million credit card numbers) went undetected for weeks despite security alerts from their FireEye monitoring system — because no one was watching the dashboard. The alerts were generated and ignored. This illustrates that logging alone is insufficient; you need alerting and response processes.
What to check:
- Are login attempts logged (successes and failures)?
- Are access control failures logged?
- Is there alerting for suspicious patterns?
- Are logs stored securely and tamper-proof?
- Is there an incident response plan?
Code example — structured security logging:
// Security event logger — structured JSON for easy alerting
function logSecurityEvent(event: {
type: 'auth_failure' | 'auth_success' | 'access_denied' | 'rate_limited' | 'suspicious_input';
userId?: string;
ip: string;
path: string;
details?: string;
}) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
level: 'security',
...event,
}));
// In production: send to SIEM (Datadog, Splunk, etc.)
}
// Usage in an auth endpoint:
if (!passwordValid) {
logSecurityEvent({
type: 'auth_failure',
ip: request.headers.get('x-forwarded-for') ?? 'unknown',
path: '/api/auth/login',
details: `Failed login for email: ${email}`,
});
}
How CheckVibe helps: The audit logging scanner checks for monitoring infrastructure and logging practices.
A10: Server-Side Request Forgery (SSRF)
Severity: High | Prevalence: #10 | Quick test: If your app fetches URLs (webhooks, image proxies, link previews), try submitting http://169.254.169.254/latest/meta-data/ and see if it returns AWS metadata
SSRF occurs when a web application fetches a remote resource without validating the user-supplied URL, allowing attackers to access internal services. SSRF was added to the OWASP Top 10 in 2021, reflecting the growing risk in cloud-native architectures where metadata endpoints and internal services are accessible from application servers.
Real-world breach: The Capital One breach (mentioned in A01) was fundamentally an SSRF attack — the attacker exploited a misconfigured WAF to make server-side requests to the AWS metadata endpoint (169.254.169.254), obtaining temporary credentials that gave access to S3 buckets containing 106 million customer records. SSRF is especially dangerous in cloud environments where metadata endpoints expose IAM credentials.
What to check:
- Do URL inputs validate against internal IP ranges?
- Are webhook URLs restricted to public addresses?
- Is DNS rebinding mitigated?
- Are cloud metadata endpoints blocked (169.254.169.254)?
- Is there an allowlist for external service URLs?
Code example — SSRF protection for URL fetching:
import { URL } from 'url';
import dns from 'dns/promises';
async function safeFetch(userUrl: string): Promise<Response> {
const parsed = new URL(userUrl);
// Block private/internal hostnames
const blockedHosts = ['localhost', '127.0.0.1', '0.0.0.0', '169.254.169.254'];
if (blockedHosts.includes(parsed.hostname)) {
throw new Error('Internal URLs are not allowed');
}
// Block private IP ranges and .internal/.local domains
if (parsed.hostname.endsWith('.internal') || parsed.hostname.endsWith('.local')) {
throw new Error('Internal domains are not allowed');
}
// Resolve DNS and verify the IP is public
const addresses = await dns.resolve4(parsed.hostname);
for (const addr of addresses) {
if (
addr.startsWith('10.') ||
addr.startsWith('172.16.') ||
addr.startsWith('192.168.') ||
addr.startsWith('127.') ||
addr.startsWith('169.254.')
) {
throw new Error('URL resolves to a private IP address');
}
}
return fetch(userUrl, { redirect: 'manual' }); // Don't follow redirects to internal URLs
}
How CheckVibe helps: The redirect scanner tests for open redirect and SSRF patterns, and the security headers scanner verifies proper configuration.
Run the Full Checklist Automatically
Going through this checklist manually for every deployment isn't realistic. That's why automated scanning exists — to run all these checks consistently, quickly, and repeatedly.
CheckVibe covers all 10 OWASP categories across its 36 scanner suite. One scan, under a minute, and you get a complete picture of where your application stands.
For deeper dives into specific vulnerability categories, see our related guides:
- OWASP Top 10 for Indie Hackers — a simplified walkthrough focused on the risks that matter most for solo developers and small teams
- SQL Injection Prevention Guide — deep dive into parameterized queries, ORMs, and WAF rules for preventing injection attacks
- How to Find XSS Vulnerabilities — practical techniques for discovering and fixing cross-site scripting in your application
FAQ
How often is OWASP Top 10 updated?
The OWASP Top 10 is updated every 3-4 years based on data from hundreds of organizations and thousands of applications. The current version was released in 2021 (a significant update that added Insecure Design, Software Integrity Failures, and SSRF as new categories). The previous versions were published in 2017, 2013, 2010, and 2007. The next update is expected in 2025-2026. Between updates, the underlying vulnerabilities do not change — the list reflects shifts in prevalence and impact data, not new vulnerability types. You should audit against the current Top 10 regardless of when the next update arrives.
Is OWASP compliance required?
The OWASP Top 10 is not a legal or regulatory compliance requirement in itself. However, it is referenced by many compliance frameworks. PCI DSS (Payment Card Industry Data Security Standard) requires that applications are developed in accordance with secure coding guidelines and specifically references OWASP. SOC 2 Type II audits frequently use the OWASP Top 10 as a benchmark for application security controls. GDPR Article 32 requires "appropriate technical measures" to protect personal data, and the OWASP Top 10 is widely accepted as a baseline for what "appropriate" means. In practice, if you pass an OWASP Top 10 audit, you are in a strong position for most compliance frameworks.
Which risk is most common?
Broken Access Control (A01) is the most common vulnerability category, with a 3.81% incidence rate across 318,000+ data points in OWASP's analysis. In CheckVibe scans specifically, Security Misconfiguration (A05) is the most frequent finding — nearly every application is missing at least one security header or has an overly permissive CORS policy. Injection (A03) remains one of the most dangerous despite being well-understood, because AI-generated code and rapid development frequently bypass parameterized query patterns. The combination of A01, A03, and A05 accounts for the majority of real-world breaches.
How do I test against OWASP?
The most practical approach is to use a combination of automated scanning and targeted manual testing. Start by running an automated scanner like CheckVibe against your live application — this covers the detectable aspects of all 10 categories in under a minute. For deeper testing, use the OWASP Testing Guide (a 400+ page document with specific test cases for each category) as a reference. For each category, try the "quick test" listed in the sections above. For injection testing, use tools like SQLMap (for SQL injection) and the XSS payloads from the OWASP Cheat Sheet Series. For access control, manually test every API endpoint with different user roles. Run npm audit for dependency vulnerabilities. Check your security headers at securityheaders.com. The key is to make testing a recurring process — not a one-time event.
Check your app against the OWASP Top 10 in 60 seconds. Start scanning with CheckVibe — free to try, no credit card required.