Forge
Security checklist

Web app security checklist: the gaps that get apps breached

Most breaches of small web apps aren't clever. They come from a route that doesn't check who is calling, a secret in the code, a query built from user input or a database left open. Here's what to check, where to look in the code, and what good looks like.

Last reviewed by the Forge team

What goes wrong

What it costs when this is missed

Critical

Broken access control

One user reading or changing another user's data. It's the top risk in the OWASP Top 10, and usually one missing line.

Critical

Leaked secrets

A key in the code, the browser or the git history lets anyone use your accounts, at your expense.

High

Injection

Queries or HTML built from user input let attackers read your database or run script in your users' browsers.

Medium

Known holes in packages

Outdated dependencies with published vulnerabilities are the easiest way in, and scanners find them automatically.

Common problems

What to look for in your code

Each one: why it hurts, and how to spot it in your own project.

01Routes and actions that don't check the caller

Every API route, server action and webhook is a public entry point, whether or not your UI links to it.

How to spot it: List every route and server action. For each one, find the line that checks the session and the user's permission.

Risky
"use server";
export async function deleteProject(id: string) {
  await db.project.delete({ where: { id } });
}
Better
"use server";
export async function deleteProject(id: string) {
  const user = await requireUser();
  await db.project.delete({ where: { id, ownerId: user.id } });
}

02Records fetched by ID alone

If /api/invoices/123 returns invoice 123 to anyone signed in, changing the number shows other people's invoices (an 'IDOR').

How to spot it: Look for queries by ID that don't also filter by the owner or the team.

03Secrets in code and history

Keys committed once stay in git history after you delete them. Keys in browser code are public.

How to spot it: Search for key-shaped strings, turn on GitHub secret scanning, and rotate anything that was ever committed.

04SQL built from user input

Concatenating input into a query lets an attacker rewrite it. Parameters keep data as data.

How to spot it: Search for queries built with + or template strings around request values.

Risky
db.query("select * from users where email = '" + email + "'");
Better
db.query("select * from users where email = $1", [email]);

05Unescaped HTML

Rendering user content as HTML lets an attacker run script in your users' browsers (XSS), with their session.

How to spot it: Search for dangerouslySetInnerHTML, innerHTML and v-html. Each one should render trusted or sanitised content only.

06Webhooks without signature checks

If your Stripe or Paddle webhook doesn't verify the signature, anyone can post a fake 'payment succeeded'.

How to spot it: Find each webhook handler and the line that verifies the signature with the provider's secret, using the raw request body.

07No rate limits on expensive or sensitive routes

Login, sign-up, password reset and AI endpoints without limits invite password guessing, spam and surprise bills.

How to spot it: Check the routes that send email, call paid APIs or accept passwords for a per-user or per-IP limit.

08Loose CORS and cookies

A wildcard CORS policy with credentials, or session cookies without HttpOnly, Secure and SameSite, makes stealing sessions easier.

How to spot it: Check the CORS config and the cookie options where the session is set.

09Tokens and personal data in logs

Logs get copied to third-party tools and kept for months. Tokens in them work for whoever reads them.

How to spot it: Search for logs of full requests, headers or user objects.

Quick self-check

How does your project do?

Tick each one that's true today. Anything left unticked is worth a closer look.

Tick what's true for your project. 9 checks.

How Forge checks this

A second opinion on your code, from the Issues scan.

  • Walks through every route, server action, middleware and auth module looking for missing checks.
  • Looks for secrets in code and logs, injection, unsafe HTML, CORS and CSRF gaps, and unchecked webhooks.
  • Ranks what it finds by impact, so what could hurt you today comes first.
  • Every finding quotes the code; Forge checks the quote exists before showing it.

Questions

Isn't a security scanner enough?

Dependency scanners find known vulnerabilities in packages. They can't tell that your delete route forgets to check the owner. You need both: a dependency check, and a review of your own code's access rules.

Are environment variables safe?

On the server, yes. Variables exposed to the browser (NEXT_PUBLIC_, VITE_ and similar) are public: anyone can read them in the downloaded JavaScript.

What should I fix first?

Anything that lets one user read or change another user's data. Then leaked secrets (rotate them; git history keeps them). Then injection and unchecked webhooks.

Related guides

All guides

Sources