StackAttestTechnical Trust
v0 security audit

A security audit for a v0 app, checked at the Next.js boundaries

The coding agent writes the software. StackAttest independently validates the software that was written. In a Next.js application that mostly means checking boundaries: which code runs on the server, which of it ships to the browser, and which of the two the application is trusting when it decides whether a request is allowed.

Scan a live appValidate a repositorySee an example Passport

Where the boundaries are checked

The boundaries live in the source, so repository validation carries most of this page. Running the application is what establishes whether the boundary holds for a request that did not come from your interface.

Repository validation

E2
Repository

Reads the source: where secrets live, whether authorisation is enforced on the server or only in the interface, whether data access is injection safe, and which dependencies carry known critical vulnerabilities. Findings cite the file they came from, so a result is checkable rather than asserted.

Runtime validation

E4
Runtime

Builds and runs the application in an isolated environment and probes the running system. This is the layer that answers questions reading code cannot: whether the deployed configuration actually refuses an unauthorised request, and whether error responses leak internals under real conditions.

Public URL scan

E4
URL scan

Tests the deployed application from outside, the way any visitor reaches it: transport security, security response headers, and what the running service discloses about itself. It needs nothing but the address, and what it observes it observes against the running system. Its limit is reach rather than strength: a URL can never establish authorisation, data-layer or backup posture, and those controls stay uncovered.

The Next.js boundaries an audit has to check

These are failure modes common in the architecture, not claims about your application. Each is a place where an app can look correct in the browser and behave differently when the request does not come from the browser. These are failure modes common to this architecture, not a claim about your application, and the audit establishes which of them are actually present.

A Server Action is a public HTTP endpoint

Marking a function with the "use server" directive publishes it. The framework gives it an identifier and the browser posts to that identifier, so anyone can post to it too. Which component calls the action, and whatever that component checked before calling it, constrains nothing. Every action needs its own authentication check, its own authorisation check and its own validation of the arguments it receives, exactly like a route written by hand.

Middleware is not an authorisation boundary

Middleware is the right place to send a signed-out visitor to the login page. It is the wrong place to decide whether this user may act on this record: it runs before the handler, without the data the decision needs, and it only covers the paths its matcher happens to match. One route added outside the matcher is not protected, and nothing about that is visible in the interface. The check that counts belongs next to the data access.

Route Handlers that trust the identifier they are given

A route file under the API directory is as public as any endpoint. Reading an identifier from the request and acting on it without checking who owns the record is the most exploited weakness in the OWASP API list, and it survives testing because the interface only ever sends the identifier it should.

The NEXT_PUBLIC_ prefix is a decision to publish

Any variable whose name starts with NEXT_PUBLIC_ is inlined into the browser bundle when the app is built. It is the most common way a secret escapes a Next.js application, and it usually arrives as a rename rather than a misjudgement: the value was needed in a component, the prefix made the error go away, and the key shipped to every visitor. Removing the prefix later does not recall the bundles already downloaded.

The client and server split, one import away

The "use client" directive moves a file and everything it imports into the browser bundle. A helper that reads a secret or holds a privileged database client is safe in a server component and exposed in a client one, and the import that crosses the line is usually two files away from anything a person reviewed. Importing the server-only package into a module that must never ship turns that mistake into a build failure instead of a deployment, and the audit checks whether the modules that need it have it.

Webhook handlers that accept what they are sent

A handler receiving payment events has to verify the signature against the raw request body, and it has to stay correct when the same event arrives twice. Generated handlers frequently parse the payload and act on it, which means anyone who learns the address can announce a payment that never happened. Stripe is the usual case and the expensive one.

What the result looks like

An illustrative extract, not a real customer result. Every row carries the evidence grade behind it, and a control nobody could establish says so.

ControlStatusEvidenceNote
Object ownership enforced server-sideFailedE2Two Server Actions act on a caller-supplied identifier
Secrets managed outside source codeFailedE2A privileged key is named with the NEXT_PUBLIC_ prefix
Server-side input validation on state-changing endpointsPartialE2Validated in the form, not in the action it posts to
Error responses do not leak internalsVerifiedE4Stack traces suppressed under induced failures
Health checks and basic observabilityUncoveredNot gradedNo evidence available from the layers that ran

Mapped to published standards

Each control is tested against a named clause, so a result means something outside our own vocabulary. Mapping is not certification, and none of these bodies endorse StackAttest.

OWASP ASVS 5OWASP API Security Top 10OWASP WSTGCWENIST SSDF

What this audit is not

  • StackAttest is not affiliated with Vercel and does not integrate with it. There is no access to your account, your deployments or your project settings; the audit works from what you give it.
  • It is not a penetration test. A deterministic check and a runtime probe are not an adversarial human engagement, and there are classes of weakness only a person hunting for them will find.
  • It is not SOC 2 and does not replace it. SOC 2 attests to organisational controls over time; this validates technical controls in the software. They answer different questions for different buyers.
  • It does not replace human due diligence. It gives a reviewer repeatable evidence to start from, which is a different thing from being the reviewer.
  • Controls the run could not reach are reported as uncovered rather than passed. A result you did not earn is worse than no result.
  • It is not a Vercel integration. There is no connection to your Vercel account, your deployments or your project settings. The audit works from the address you give it, the repository you connect, and the application it builds and runs in isolation.

Questions

What is a v0 security audit?

An independent check of an application generated with v0, tested against named controls and focused on the boundaries a Next.js app is judged on: what runs on the server, what ships to the browser, and where the decision to allow a request is actually made. Every result records the strength of the evidence behind it.

Do you connect to my Vercel account?

No. There is no Vercel integration, no access to your deployments and no read of your project settings. You give us a public address, or connect a repository read-only, and the audit works from those.

Are Server Actions safe by default?

They are convenient by default. A Server Action is a public endpoint the framework routes for you, so the arguments arrive from the network rather than from your component, and the caller is whoever posts to it. The controls that apply to any endpoint apply here, and the audit checks whether they are present in each one.

Is middleware enough to protect my routes?

It is enough to redirect a signed-out visitor, which is what it is good at. It is not enough to decide whether a signed-in user may act on a particular record, and a route outside its matcher does not run it at all. Treat it as routing with a login redirect, and put the decision that matters next to the data.

How do I know if a key leaked into the browser bundle?

That is one of the things repository validation looks for: which values carry the NEXT_PUBLIC_ prefix, and whether any of them is a credential that would bypass your access rules. If one is, the finding cites the file, and the first remediation step is rotating the key rather than deleting the line.

Can you test the deployed app without the source?

Yes. A URL scan tests the running deployment from outside and runtime validation probes its behaviour, and both produce evidence about the deployed system rather than the code. The report is explicit about which layers ran, and controls they could not reach are listed as uncovered.

Related

Claude Code security auditVibe code auditAI app security auditAI code auditFree vibe coding checklistSecurity and data handling

Find out what is actually true of your application

Start with the layer you can run today. The report says which layers ran and what they could not reach, so the result is honest about its own limits.

Scan a live appValidate a repository