Common Vulnerabilities
The most common web app attacks explained simply, and how to avoid them.
These are the attacks that hit small web apps most often. Understanding them helps you avoid writing vulnerable code.
XSS (Cross-Site Scripting)
What it is: An attacker injects JavaScript into your page that runs in other users' browsers.
How it happens: Your app displays user input without sanitizing it.
- User input is HTML-escaped before display - Using framework's built-in escaping (React's JSX
does this automatically) - Never using
dangerouslySetInnerHTMLwith user data - Content Security Policy blocks inline scripts
- Using
innerHTMLwith user-submitted data - Building HTML strings from user input - No CSP to limit script execution - Trusting URL parameters in page content
XSS vulnerabilities are critical. If you're using React or another modern framework, you're mostly protected by default — just don't bypass the safeguards.
The good news: If you're using React, Vue, or Svelte, your framework automatically escapes user input in templates. Just don't use dangerouslySetInnerHTML or v-html with untrusted data.
CSRF (Cross-Site Request Forgery)
What it is: An attacker tricks a logged-in user into making requests they didn't intend to (like changing their password or deleting their account).
How it happens: A malicious site submits a form to your API using the user's browser cookies.
- Using CSRF tokens in forms - SameSite cookie attribute set to "Lax" or "Strict" - Checking Origin/Referer headers on mutations
- No CSRF tokens on forms - Cookies set without SameSite attribute - State-changing actions via GET requests
The good news: Modern frameworks (Next.js, SvelteKit) handle CSRF protection automatically for server actions. If you're using API routes, set SameSite=Lax on your cookies.
SQL/NoSQL Injection
What it is: An attacker crafts input that changes your database query, potentially reading or deleting data.
How it happens: User input is concatenated directly into a database query string.
- Using parameterized queries or an ORM - Input validation before database queries - Principle of least privilege for database users
- Building query strings with user input - No input validation - Database user has admin privileges
The good news: If you're using an ORM (Prisma, Drizzle) or a platform like Convex, you're protected by default. The ORM handles parameterization.
Exposed secrets
What it is: API keys, database credentials, or other secrets accidentally included in client-side code.
- API keys in environment variables (not in code) - Server-only secrets use
PRIVATE_or non-NEXT_PUBLIC_prefixes -.envfiles in.gitignore- No secrets in client-side JavaScript bundles
- API keys hardcoded in JavaScript files -
.envfile committed to git -NEXT_PUBLIC_prefix on private keys - Secrets visible in browser DevTools
Exposed secrets are an automatic critical finding. Check your browser's DevTools > Network tab to see what gets sent to the client.