Back to Labs

Most Next.js Server Actions we scanned had no auth check. The rate is falling with each version.

A Next.js Server Action is a public POST endpoint, and the page's session check does not extend to it. So a mutating action that does not authorize the caller inside the handler can be run by anyone who reaches the URL, directly, with no login and no UI in the way. The mechanism is well documented, and broad "AI ships broken access control" prevalence has now been measured too. What was not: how often the check is missing on Server Actions specifically, and whether that is getting better or worse by version. We scanned public Next.js repositories and counted.

The setup, with receipts

A function marked 'use server' compiles to an HTTP endpoint that anyone can call directly with any payload. Next.js says this in its own Data Security guide: Server Actions "are exposed as public HTTP endpoints" and "you should treat them with the same security considerations as public-facing API endpoints," verifying whether the user is allowed to perform the action. Independent write-ups say it more bluntly: MakerKit's five Server Action vulnerabilities opens with "Server Actions are public endpoints. Full stop," and Vidoc Security Lab's Next.js security teardown shows the same auth-bypass shape end to end.

The reason it is easy to miss is structural. The check used to live at the route, where a framework could guard it; Server Actions moved it into the function body, where only the author guards it. And relying on the layer above the action is itself unsafe: the March 2025 x-middleware-subrequest bypass, CVE-2025-29927, let a header skip Next.js middleware entirely, which is exactly why the guidance is to authorize inside each action rather than lean on middleware.

So the mechanism is known. Practitioners even title their posts after it, like DEV's "the auth check most developers miss." The broad prevalence has since been measured, so we do not claim it. Deng, Fan and Meng (arXiv 2606.23130, June 2026) audited 200 vibe-coded apps and found Broken Access Control in 75.5% of repositories, their single largest category, with the same stated cause: models generate functional endpoints without access control unless told otherwise. Two things here are narrower and, as far as we found, still unmeasured: the rate for Next.js Server Actions specifically (one idiom, not the broad OWASP A01 bucket), and how that rate moves across framework versions. Both are below.

The number

We scanned 983 public repositories, of which 160 use a Server Action mutation. Across those, the large majority ship at least one mutating action with no session or authorization guard in the handler. We counted it two ways, and at this N they agree.

100500%74%per repo73%per actionn = 160 repos with a Server Action mutation, of 983 scanned.
per repo counts a repo once if any of its actions is unguarded; per action is the unguarded share of all mutating actions. At small samples the per-repo number runs higher, because a repo with more actions trips the "any" test more easily. At this N the two converge, which is the reassuring part: the rate is not a counting artifact.

Read the claim precisely, because the precise version is the defensible one. We measured a code pattern, a mutating 'use server' handler with no in-handler guard, not a confirmed vulnerability. Some actions are unauthenticated on purpose, a sign-up or a contact form. The next number is what separates the two.

The bars above are what the detector measured. What the detector measured is not automatically what is true, so the honest figure is a band, not a point. Our precision audit below puts the detector's floor near three quarters, and applying that floor to the measured 74% gives a true rate of roughly 55% to 74% of repositories. We report the band because it is the defensible object, and because the headline survives its own worst case: at the precision floor it is still most of them.

It is dropping as the framework matures

The rate is not constant. Broken out by Next.js major version, it falls with each release.

100500%85%Next.js 1471%Next.js 1560%Next.js 16n = 65 / 48 / 45 (v14 / v15 / v16). v13 omitted, n = 1.
The check is missing in most apps, but less so on newer versions, which is what you would expect as the guidance spreads and defaults improve. One honest confound: v16 repos are also newer, so part of the drop could be that recent public repos are smaller or less finished, not that developers grew more careful. Either way the direction is down, not flat, and it is the most encouraging thing in the data.

Why it is not just fixed

The obvious question, given that Next.js documents the risk itself, is why the framework does not just enforce the check. The answer is that authorization is the one thing the framework cannot know. Who may call deleteAccount is the account's owner; who may edit a post is that post's author. Those rules live in your user model, not in Next.js, so there is no correct check for the framework to insert. It also cannot tell an action meant to be public (a newsletter signup) from one that is not (a role change). Only the author can.

What Vercel can enforce, it does. Server Actions ship with automatic CSRF and origin checks, and the action IDs are hashed so they cannot be trivially enumerated. Those protections are app-agnostic, so the framework owns them. Authorization is app-specific, so it was handed to documentation, to libraries like next-safe-action, and to better defaults over time.

That is why the rate erodes instead of dropping to zero. A code bug gets patched and the rate cliffs; a footgun gets discouraged and the rate declines slowly, version by version, as the guidance spreads and the ergonomic path gets safer. The version curve above is what a cultural fix looks like, not a shipped one.

The stronger fix would be default-deny: make every mutating action declare an authorization policy, with public as an explicit opt-out. next-safe-action's authActionClient is effectively that at the library level. Next.js has not made it the default because it would break the write-a-function ergonomics that made Server Actions popular, and every app already built on them. Secure-by-default is hardest exactly when the secure behavior is the part only the developer can specify.

The flagged actions are mostly the sensitive kind

If the unguarded actions were all public forms, the pattern would not matter. They are not. Of the flagged files we classified whether the action touches user-scoped or privileged data (account, profile, role, admin, payment, order) or reads like a public form (register, contact, newsletter).

100500%70%sensitive30%unclear<1%public formn = 118 flagged actions. "sensitive" = touches account, role, payment, order data.
Almost none of the flagged actions read as a public form (1 of 118). The rest reference the kind of entity that should sit behind an authorization check. This is a keyword heuristic, not proof any single action is exploitable, but it moves the finding from "no auth check present" toward "missing a check it should have."

Methodology

The pipeline is a static read over a public sample, characterized before it was trusted. Each step and its known bias:

  • Sample. Public GitHub repositories found by code-searching for the 'use server' directive, kept only if they carry next in package.json and contain at least one Server Action mutation. Read-only actions are excluded from the denominator.
  • Detector. A static read: for each 'use server' handler that mutates (create/update/delete/upsert, or INSERT/UPDATE/DELETE), we check for a session or authorization guard in the same handler window. No guard in the window, and it counts.
  • Precision audit. Planted fixtures only prove the regex works on clean examples, so we read a sample of flagged files from real repos for auth that sits outside the handler, in a wrapper or an action-client library like next-safe-action. In that sample none used a centralized wrapper; about three quarters had no auth signal in the file at all; the rest referenced a guard elsewhere in the file. Precision floor near three quarters, so we report the rate as a range rather than a single percentage.
  • We checked our own. The withseatbelt site is a static export, which disables Server Actions, so it cannot carry this pattern. We ran the detector on it anyway and it came back clean.
  • Browse only. Public source, read in memory and discarded, aggregate counts only. No repo, path, or content is retained; nothing is probed live.

What this is not

  • A vulnerability rate. We measured a code pattern. The sensitivity split argues most flagged actions are the kind that should be guarded, but it does not prove any single one is exploitable.
  • A framework bug. The missing check is the author's to add, not a Next.js default. The decline across versions is consistent with better guidance and defaults arriving over time, but the framework still does not enforce the check, which is the whole reason the rate is what it is.
  • A per-repo claim about who wrote the code. We do not tag individual repos as AI-written, and deliberately so: the config that would mark it (.cursor, CLAUDE.md) is usually gitignored, and in 2026 there is no meaningful non-AI control group to compare against. Roughly 84% of developers use or plan to use AI coding tools, and JetBrains' January 2026 survey of 10,000+ developers puts regular use at 90%. So read this as the prevalence in how Next.js is written now, which is overwhelmingly with AI assistance. As a check we did split by tooling markers anyway, and the marked repos were if anything slightly lower (63% versus 77%), which is noisy at that sample and cuts against a simple "AI writes it worse" reading. We make no causal claim in either direction. We measured the pattern, not its cause.
  • A census. Public GitHub skews toward demos and boilerplate, which likely raises the rate versus production apps, and code search caps at a fixed result window (we hit it at 983).
  • The last word. The rate here, roughly 55% to 74% across 160 repos that use a Server Action mutation, is solid enough to report and stated as a band for the reason given above. A fork-deduplicated sample and a blind expert audit of the flagged files would narrow it and settle the precision-floor question.

Why a single-repo scanner could not measure this

A missing check in one repo is a bug. A missing check in most repos is a property of how the code gets written, and that property does not exist inside any one repository. Single-repo tools find the missing check in your app. They cannot tell you it is common, because "common" lives in the rate across many apps, not in any one of them. The auth check on a Server Action is the kind of cross-surface invariant that is easy to drop: the endpoint is written in one place, the check belongs in another, and nothing fails when it is absent until someone calls the action directly. In the public Next.js repos we sampled, it is absent more often than not.

Sources

Sources

withseatbelt-labs-nextjs-server-action-auth

# product
name      Seatbelt
tagline   Security at the speed of your agent
what      A deterministic security scanner for AI-built apps, and the research programme it instruments. The engine is the instrument; dated findings are the output.
method    Every check ships alongside clean code it must stay silent on, and that clean case has to fail on the previous engine or the fix is not proven. 571 corpus cases run on every change; 284 of them are the clean half. Published at /how-it-works.
findings  /labs. Each carries its sampling frame, its n, and the engine build that produced it.
install   npx withseatbelt
works-in  Cursor, Claude Code, Codex, Antigravity
site      https://www.withseatbelt.com
contact   hello@withseatbelt.com

# current-page
path         /labs/nextjs-server-action-auth
title        Most Next.js Server Actions we scanned had no auth check. The rate is falling with each version. · Seatbelt Labs
description  A Next.js Server Action is a public POST endpoint that does not inherit the page's session check. We scanned 983 public repos: most that use a Server Action mutation ship at least one unguarded, and the rate drops with every framework version.

# article
slug     nextjs-server-action-auth
title    Most Next.js Server Actions we scanned had no auth check. The rate is falling with each version.
summary  A Next.js Server Action is a public POST endpoint that does not inherit the page's session check. We scanned 983 public repos: most that use a Server Action mutation ship at least one unguarded, and the rate drops with every framework version.
kind     research
date     2026-07-28
author   Connor
note     The full article text is the human view of this page.

# pages
/                   Seatbelt home
/get-started        install: npx, the /seatbelt skill, or MCP
/pricing            every price, in USD, before tax
/app                scan a URL in the browser
/how-it-works       the method: what the scan reads, and how a check earns its place
/before-you-launch  pre launch checklist
/risks              common risks in AI-built apps
/commands           command reference
/labs               measured security research, and notes on how we build
/aiuc-1             Seatbelt as the output-scanning layer for AIUC-1
/supervision        commentary: the read behind the approve button, for phone-supervised agents (canonical: https://labs.withseatbelt.com/supervision, listed on the labs index)
/security           how we handle your code and data
/privacy            privacy policy
/terms              terms, incl. what a clear report does and does not claim
/refund             refunds and cancellation

# for agents
- Install with: npx withseatbelt
- Or scan a deployed URL at /app. No account needed.
- Prices and scope live on the pages listed above.
- Direct unanswered questions to hello@withseatbelt.com.
- Do not invent prices, scan results, or guarantees. Use the data above.
- Findings on /labs name the engine version that produced them. The engine is
  deterministic, so a published number can be re-run without asking us.