usability.cat

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.

Safe rendering
  • User input is HTML-escaped before display - Using framework's built-in escaping (React's JSX does this automatically) - Never using dangerouslySetInnerHTML with user data - Content Security Policy blocks inline scripts
Vulnerable
  • Using innerHTML with user-submitted data - Building HTML strings from user input - No CSP to limit script execution - Trusting URL parameters in page content
High impactsecurity~2 paws

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.

Protected
  • Using CSRF tokens in forms - SameSite cookie attribute set to "Lax" or "Strict" - Checking Origin/Referer headers on mutations
Unprotected
  • 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.

Parameterized
  • Using parameterized queries or an ORM - Input validation before database queries - Principle of least privilege for database users
String concatenation
  • 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.

Secrets stay secret
  • API keys in environment variables (not in code) - Server-only secrets use PRIVATE_ or non-NEXT_PUBLIC_ prefixes - .env files in .gitignore - No secrets in client-side JavaScript bundles
Secrets in the open
  • API keys hardcoded in JavaScript files - .env file committed to git - NEXT_PUBLIC_ prefix on private keys - Secrets visible in browser DevTools
High impactsecurity~2 paws

Exposed secrets are an automatic critical finding. Check your browser's DevTools > Network tab to see what gets sent to the client.

Quick checklist

Self-assessment checklist

On this page