0%
💸 Launch Offer for the first 50 founders: Save at least $132.
Auth & Security#Security#SaaS#OWASP

The OWASP Top 10:2025 Checklist for SaaS. And How to Actually Cover it?

Alap Putatunda

Written by AI &

Alap Putatunda

June 1, 2026
An illustration of a laptop protected by a blue shield with a padlock and fingerprint scanner, next to a yellow folder with a skull icon, representing OWASP security checks for SaaS.
An illustration of a laptop protected by a blue shield with a padlock and fingerprint scanner, next to a yellow folder with a skull icon, representing OWASP security checks for SaaS.

TLDR: The OWASP Top 10:2025 is the 8th installment of the list, and it changes the game for SaaS teams: it adds two brand-new categories — A03 Software Supply Chain Failures and A10 Mishandling of Exceptional Conditions — and folds SSRF into A01 Broken Access Control. This post maps all ten risks to a concrete, verifiable Next.js control. Vibe44 ships that coverage out of the box: row-level security across 32 tables, HMAC-bound CSRF, SSRF guards on outbound fetches, hardened headers, and an auth core — all backed by 900 unit tests and 9 Playwright E2E suites, for a $197 one-time license.

"We'll add security later" is how SaaS dies on launch day. A single tenant-isolation bug returns 200 OK while quietly handing one customer another customer's data, and you find out from a support email, not a stack trace.

The good news: you don't have to guess what to defend. OWASP already enumerated the ten things most likely to get you owned, and in 2025 they reordered the list to match how modern apps actually break. The bad news: most "Next.js security" advice still maps to the 2021 list, so it's quietly out of date.

Here's the 2025 OWASP Top 10 translated into concrete Next.js controls you can verify against your repo today — and where a hardened boilerplate already covers them.

What is the 2025 OWASP Top 10, translated for Next.js?

The OWASP Top 10 is a consensus ranking of the most critical web application security risks, refreshed roughly every few years from real-world breach and vulnerability data. The 2025 edition — the 8th installment — is not cosmetic. Three changes matter most for anyone shipping a Next.js SaaS:

  • SSRF is now consolidated into A01 Broken Access Control. In 2021 Server-Side Request Forgery had its own slot. In 2025 it lives under A01, which makes sense: an attacker steering your server's outbound request is, fundamentally, accessing something they shouldn't. For Next.js this is huge, because Server Actions, Route Handlers, and any fetch you make on the server are exactly the surface SSRF abuses.
  • A03 Software Supply Chain Failures is new, expanding 2021's narrower "Vulnerable and Outdated Components." Your node_modules tree, your lockfile, your build pipeline, and your CI tokens are now first-class attack surface — not a footnote.
  • A10 Mishandling of Exceptional Conditions is new, calling out what happens when your app hits an error path: leaked stack traces, fail-open logic, and unhandled rejections that crash a route or expose internals.

Also note A02 Security Misconfiguration moved up (it was #5 in 2021), and A07 was renamed to Authentication Failures. Below is the answer-first map: every 2025 risk, and the concrete Next.js control that covers it.

OWASP Top 10:2025 riskConcrete, verifiable Next.js control
A01 Broken Access Control (now includes SSRF)Enforce authorization at the database with row-level security (RLS), not just if (user.id === ownerId) checks scattered in handlers. Re-verify ownership inside every Server Action. For the SSRF half: allowlist outbound hosts, block private/link-local IP ranges, and refuse redirects to internal targets.
A02 Security MisconfigurationShip hardened response headers via middleware.ts — a strict Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, Referrer-Policy, and X-Frame-Options. Disable verbose error output and source maps in production. Lock down CORS to known origins.
A03 Software Supply Chain FailuresCommit a lockfile, pin dependencies, and run npm audit plus an automated dependency scanner in CI. Gate merges on a green security check. Treat your CI tokens and build secrets as production credentials.
A04 Cryptographic FailuresForce TLS everywhere via HSTS. Hash passwords with a slow algorithm (let Supabase handle it). Sign and verify any token you mint — including CSRF tokens — with HMAC. Never log secrets or tokens.
A05 InjectionUse a parameterized query layer (Drizzle ORM) so user input never concatenates into SQL. Validate and coerce every input at the boundary with a schema validator. Escape output; rely on React's default escaping and a strict CSP to blunt XSS.
A06 Insecure DesignThreat-model the feature before you build it. Default-deny access. Bind sensitive actions to the session. Rate-limit auth and write endpoints. Design tenant isolation into the schema, not as an afterthought patch.
A07 Authentication FailuresUse a vetted auth core: short-lived rotating sessions, MFA, OAuth with PKCE, email verification, password-reset that doesn't leak account existence, and rate-limited login. Don't roll your own session handling.
A08 Software or Data Integrity FailuresVerify webhook signatures (Stripe, Lemon Squeezy, Polar, Dodo, Creem) before trusting any event, and make handlers idempotent. Validate integrity of anything you deserialize. Pin and verify your build artifacts.
A09 Security Logging & Alerting FailuresEmit structured, security-relevant logs — failed logins, access denials, privilege changes — without leaking PII or secrets. Wire alerting so a spike in 401s or 403s pages a human, not a dashboard nobody watches.
A10 Mishandling of Exceptional ConditionsFail closed. Catch errors at route and action boundaries, return a generic message to the client, and log the detail server-side. Handle unhandled promise rejections. Never let an error path silently grant access or expose a stack trace.

That table is the whole job, stated plainly. The rest of this post is about where Next.js apps actually fail against it — and how to verify your coverage.

Where do Next.js apps fail most against the OWASP Top 10?

Knowing the list isn't the hard part. The hard part is that Next.js gives you a half-dozen comfortable ways to do the right thing and the wrong thing, and the wrong way usually still returns 200 OK. Here's where I see real apps get owned, mapped back to the 2025 categories.

Authorization that lives in the wrong layer (A01). The single most common failure: ownership checks scattered across Route Handlers and Server Actions, one if statement at a time. Miss one — and on a fast-moving codebase you will — and you've shipped an Insecure Direct Object Reference. Anyone can read record 124 by asking for it. The fix is to push authorization down to the database with row-level security so the data layer itself refuses to return another tenant's rows, no matter which handler forgot to check. Application-layer checks are defense in depth on top of RLS, never instead of it.

SSRF hiding in "helpful" features (A01). This is the silent SaaS killer, and 2025 promoting it into A01 should make you nervous about every server-side fetch in your app. In early 2026, SSRF attack attempts surged by 452%, as attackers increasingly use them to bypass perimeter firewalls and target cloud metadata endpoints (like 169.254.169.254 for AWS/Azure). Your avatar-import-from-URL, your link-preview, your webhook-tester, your "fetch metadata from this URL" feature — each one takes a user-supplied URL and asks your server to request it. Point that at an internal http://localhost: admin port and your server happily reads cloud metadata or hits an internal service from inside your trust boundary. The defense is an outbound guard: resolve the host, reject private and link-local IP ranges, enforce an allowlist, and refuse to follow redirects into internal space. We go deep on this in SSRF: The Silent SaaS Killer — Hardening Outbound Requests in Next.js.

Injection through the "convenient" query (A05). Drizzle and parameterized queries make injection hard — until someone reaches for a raw SQL escape hatch to ship a feature on a deadline and interpolates a user string. One template literal with user input in it and you're back in 1998. Keep input on the parameterized path, and validate every payload with a schema at the boundary so malformed or hostile input never reaches your query layer in the first place.

CSRF "protection" that doesn't actually bind (A04/A01). Plenty of Next.js apps use double-submit cookies and call it done. The problem: a plain double-submit token only proves the request carried a value that matches a cookie — it doesn't prove that token belongs to this session. Subdomain takeover or a cookie-injection bug can defeat it. The stronger pattern is an HMAC-bound token cryptographically tied to the session, so a stolen or forged value won't validate. That's a whole topic on its own — see CSRF in Next.js: Why Double-Submit Cookies Aren't Enough (HMAC-Bound Tokens).

Missing or sloppy headers (A02). Ship a Next.js app with no middleware.ts header policy and you've left clickjacking, MIME-sniffing, and a wide-open script policy on the table. A02 moving up the list in 2025 is OWASP telling you misconfiguration is now one of the most exploited categories — and headers are the cheapest fix you're skipping.

Secrets and supply chain (A03/A04). A .env committed to the repo, an API key pasted into client-side code (remember: anything not prefixed for the server is shipped to the browser), an unpinned dependency, a CI token with more scope than it needs. A03 being brand-new in 2025 means your lockfile and pipeline are now scored risks, not hygiene suggestions.

Auth you rolled yourself (A07). Session fixation, tokens that never rotate, OAuth state and redirect handling done by hand — this is exactly where teams get subtly owned, because the happy path works perfectly in the demo. Use a vetted core. Auth That Doesn't Leak: Sessions, MFA & PKCE on Supabase + Next.js covers the correct shape.

The pattern across all of these: the insecure version compiles, passes a casual manual test, and ships. You need controls that are on by default, not controls you remember to add.

How does Vibe44 align with the OWASP Top 10 (2025) out of the box?

This is the part of the build-vs-buy math founders underestimate. You can implement every control in that table yourself — it's roughly 50 hours of senior security work to get a first version, plus the ongoing tax of keeping it current as the threat list shifts. Or you start from a foundation where the defaults are already aligned. Here's exactly what Vibe44 ships against the 2025 list, with verifiable specifics — no hand-waving.

Row-level security across 32 tables (A01). Authorization is enforced at the database, not sprinkled through handlers. RLS policies cover all 32 tables, so the data layer itself refuses cross-tenant reads even when an application-layer check is missing. This is the structural defense against Broken Access Control and IDOR, and it's the difference between "we check ownership in most places" and "the database physically can't hand over the wrong row."

HMAC-bound CSRF (A04/A01). CSRF tokens are cryptographically bound to the session with HMAC — not bare double-submit cookies that only prove a value round-tripped. A forged or stolen token won't validate against the session it's checked against.

SSRF guards on outbound requests (A01). Outbound fetches run through guards that block requests to private and link-local address space and refuse to be steered at internal targets — closing the avatar-import / link-preview / metadata-endpoint class of attacks that 2025 promoted into A01.

Hardened response headers (A02). A strict header policy ships in middleware.ts — CSP, HSTS, anti-clickjacking, MIME-sniffing protection, and referrer policy — so the misconfiguration category is covered on the first request, not bolted on before launch.

An auth core that doesn't leak (A07). Sessions, MFA, and OAuth with PKCE are implemented in the core on Supabase (PostgreSQL 15), so you inherit rotating sessions, multi-factor, and correct PKCE redirect/state handling instead of re-deriving the parts everyone gets subtly wrong.

Parameterized data access (A05). Drizzle ORM is the default query path, keeping user input off raw SQL and on parameterized queries, with schema validation at the boundary.

Signed, idempotent payment webhooks (A08). The Universal Payment adapter verifies provider signatures (Stripe, Lemon Squeezy, Polar, Dodo, and Creem) and treats handlers idempotently, so a replayed or forged event can't be trusted and a duplicate delivery can't double-grant a plan.

Proof it actually holds: 900 unit tests (Vitest) and 9 E2E suites (Playwright). Security controls are worthless if a refactor silently breaks them. The test suite is what keeps RLS policies, CSRF binding, and auth flows from regressing — and it's what lets you change code without praying you didn't reopen a hole. You can poke at the running app at demo.vibe44.com.

What's inside Vibe44's security and auth directories?

Hardening you can't read is hardening you can't trust. The point of consolidating security and authorization into dedicated directories (lib/security/ and lib/auth/) — with the request-time enforcement wired through middleware.ts — is that you can audit it, map each module to an OWASP risk, and extend it without reverse-engineering scattered logic. Here's the tour, organized by which 2025 risk each piece answers.

The auth core (A07 Authentication Failures). Located in lib/auth/, this is where session creation, rotation, MFA, and PKCE-based OAuth live. Because it sits in one place on top of Supabase, the session lifecycle — issue, rotate, expire, revoke — is consistent across every route instead of re-implemented per feature. Password reset and email verification flows are designed not to leak whether an account exists, which is the quiet detail that turns a reset form into an account-enumeration oracle.

The CSRF module (A04 / A01). HMAC-bound token generation and verification, implemented in lib/auth/csrf.ts. A token is derived from and checked against the session, so the verification step proves this session issued this token — not merely that a cookie value came back. This is the module that upgrades you past double-submit's limitations.

The SSRF guard (A01 Broken Access Control). The outbound-request gatekeeper in lib/security/ssrf.ts. Before your server fetches a user-influenced URL, this resolves the destination, rejects private and link-local IP ranges, and refuses redirects into internal space. It's the layer standing between your "fetch this URL" feature and your cloud metadata endpoint.

The headers layer, enforced in middleware.ts (A02 Security Misconfiguration). Middleware runs on the edge of every request, which makes it the correct place to attach the response-header policy: CSP, HSTS, frame and MIME protections, referrer policy. Putting it in middleware means there's no route you can forget to protect — coverage is global by construction.

Input validation and the parameterized data path (A05 Injection). Schema validation at the boundary coerces and rejects malformed input before it reaches business logic, and Drizzle keeps queries parameterized. Together they shut the door on the two most common injection vectors: hostile payloads and string-concatenated SQL.

Logging and exceptional-condition handling (A09 / A10). Security-relevant events are logged in a structured form without dumping secrets, and error boundaries fail closed — generic message to the client, detail to the server log — so an exception can't leak a stack trace or accidentally fall through into a granted permission.

The supply-chain (A03) and integrity (A08) controls live a little wider than lib/security/ — A03 in the lockfile and CI policy, A08 in the payment adapter's signature verification — but the request-time defenses are all in one directory you can open and read.

What's your pre-launch security audit checklist?

Here's the copy-paste pass to run against your own repo before you ship — whether you built from scratch or started from a boilerplate. Each item maps to a 2025 risk. If you can't check it, you have a gap.

  • [A01] Authorization at the database. RLS (or equivalent enforced policy) on every tenant-scoped table, plus an ownership re-check inside every Server Action. Don't rely on application if checks alone.
  • [A01 / SSRF] Outbound request guard. Every server-side fetch of a user-influenced URL goes through a guard that blocks private/link-local IPs and refuses internal redirects. Audit each "fetch a URL" feature individually. (Deep dive: SSRF: The Silent SaaS Killer — Hardening Outbound Requests in Next.js.)
  • [A02] Hardened headers in middleware.ts. CSP, HSTS, X-Content-Type-Options, Referrer-Policy, X-Frame-Options. Verify them on a live response, not just in config.
  • [A02] Production hygiene. Verbose errors and source maps disabled in prod. CORS locked to known origins.
  • [A03] Supply chain. Lockfile committed, dependencies pinned, npm audit and a dependency scanner gating CI. CI tokens scoped minimally.
  • [A04] Crypto and secrets. TLS forced via HSTS. No secrets in the repo. No keys shipped to the client bundle. CSRF tokens HMAC-signed. (CSRF detail: CSRF in Next.js: Why Double-Submit Cookies Aren't Enough (HMAC-Bound Tokens).)
  • [A05] Injection. Parameterized queries only — grep for raw SQL escape hatches. Schema validation on every inbound payload.
  • [A06] Insecure design. Default-deny access, rate limits on auth and write endpoints, tenant isolation designed into the schema.
  • [A07] Auth core. Rotating sessions, MFA available, OAuth with PKCE, reset/verification flows that don't leak account existence. (Detail: Auth That Doesn't Leak: Sessions, MFA & PKCE on Supabase + Next.js.)
  • [A08] Integrity. Payment webhook signatures verified and handlers idempotent. Anything deserialized is integrity-checked.
  • [A09] Logging & alerting. Structured security logs without PII/secrets, with alerting on 401/403 spikes and privilege changes.
  • [A10] Exceptional conditions. Error boundaries on routes and actions, fail-closed defaults, unhandled rejections handled, no stack traces to clients.

If that list feels long, that's the point — it's the part of "production-ready" that doesn't show up in a demo. For the full launch picture beyond security, see The Production-Readiness Checklist: 27 Things Your Next.js SaaS Needs Before Launch.

The honest cost comparison

Run the math the way an attacker forces you to. A single security audit or penetration test runs into the thousands of dollars before anyone writes a remediation ticket. A breach is worse — disclosure costs, customer churn, the engineering scramble, and the trust you don't get back. And the breaches in this layer are the quiet kind: a cross-tenant read returning 200 OK, found in a customer email six months after launch.

Against that, building the controls yourself is roughly 50 hours of senior work for a first version — and then a permanent maintenance tax as the threat list shifts (the 2025 reshuffle is proof it shifts). Starting from a foundation that's already OWASP Top 10 (2025)-aligned — RLS across 32 tables, HMAC-bound CSRF, SSRF guards, hardened headers, an auth core, all held in place by 900 unit tests and 9 E2E suites — costs a $197 one-time license.

Security isn't the feature customers pay for. It's the thing whose absence ends your company on launch day. Buy the foundation; spend your hours on the product only you can build.

Run the security checklist against your repo — or start aligned at vibe44.com.

Frequently asked questions

1. Is the OWASP Top 10:2025 different from the 2021 list?

Yes, and the differences matter for Next.js. The 2025 edition is the 8th installment. It adds two new categories — A03 Software Supply Chain Failures and A10 Mishandling of Exceptional Conditions — and consolidates SSRF into A01 Broken Access Control. A02 Security Misconfiguration moved up from #5, and A07 was renamed to Authentication Failures. Advice mapped to the 2021 list is now out of date.

2. Why is SSRF under A01 Broken Access Control in 2025?

Because steering a server's outbound request is, at root, accessing a resource you shouldn't. In 2021 SSRF had its own slot; in 2025 it sits inside A01. For Next.js this is critical, since Server Actions and server-side fetch are exactly the surface SSRF abuses — pointing your server at a cloud metadata endpoint or an internal port. The control is an outbound guard that blocks private and link-local IP ranges and refuses internal redirects.

3. Does row-level security replace application-layer authorization checks?

No — RLS is the strong floor, application checks are defense in depth on top. RLS enforces authorization at the database so a forgotten handler check can't leak cross-tenant data, which is why Vibe44 applies it across all 32 tables. You still re-verify ownership inside Server Actions. The mistake is doing only the application-layer checks and skipping the database floor.

4. Why aren't double-submit CSRF cookies enough?

A plain double-submit token only proves a value round-tripped between a cookie and the request — not that the token belongs to this session. Subdomain takeover or cookie injection can defeat it. HMAC-bound tokens are cryptographically tied to the session, so a forged or stolen value won't validate. That's the approach Vibe44 ships and the focus of our dedicated CSRF guide.

5. Can I verify a boilerplate actually covers these controls?

Yes — that's the whole point of consolidating hardening into a readable lib/security/ directory with enforcement wired through middleware.ts, and backing it with tests. Vibe44 ships 900 unit tests (Vitest) and 9 E2E suites (Playwright) so the RLS policies, CSRF binding, and auth flows don't silently regress, and you can exercise the live app at demo.vibe44.com.

Share this article

Explore More from the Vibe44 Blog

A graphic depicting a hacker at a laptop executing an SSRF attack, with numbered arrows showing the request flow between the attacker and two servers.
Auth & SecurityMay 22, 2026

SSRF — The Silent SaaS Killer. How to Harden Outbound Requests in Next.js?

SSRF lets attackers weaponize your own server to read internal services, cloud metadata, and private APIs — all through a URL you fetch on their behalf. Naive allowlists fail the moment DNS rebinding or redirect chaining enters the picture. The fix is resolve-then-pin DNS validation, hard IP-range blocking, and redirect caps — not regex on a string.

A blueprint of a server rack being scanned by a glowing AI eye.
AI & AgentsJune 13, 2026

How to Optimize Your SaaS for ChatGPT, Claude, Perplexity, etc. in 2026?

Building for AI LLMs like ChatGPT, Claude, Perplexity, etc. isn't about keyword density; it's about making your architecture structurally sound for AI consumption. With ChatGPT processing 250–500M weekly queries and capturing ~17% of global digital query share, SaaS founders who don't architect for answer engines are invisible to the next generation of buyers.

A diagram showing how Vibe44 is built for AI agents, depicting various AI agent logos pointing to a Vibe44 MCP Server, which in turn connects to Vibe44 documentation resources.
AI & AgentsJune 11, 2026

From Idea to Paying Users in a Weekend — An AI-Agent-Driven Building System.

You can build SaaS with AI in a weekend, but only if your agent starts on a real foundation. Vibe44 ships that foundation — AGENTS.md + CLAUDE.md, the Graphify Context Engine (up to 71.5x token cost reduction), a public docs MCP server, and 900 unit tests plus 9 E2E suites that catch agent mistakes before prod.