Sample report

Launch Audit report: an open-source Next.js app

This is a sample, published so you can see exactly what a paid Launch Audit gives you before you pay. It is a real audit of a public open-source project (MIT licence), not a customer's app. Nobody paid for it, and nobody's live site was touched: the code was read on my machine only.

Why the project is not named: the problem below is still in the project's code and has not been fixed yet. Many people have copied it into their own apps, so the project name, file paths and exact code are left out until its maintainer has been told and has had time to fix it.

Prepared by
Vishwas, Furnace
Date
18 October 2026
Stack
Next.js, Prisma, a sign-in library, Stripe
Tools
Furnace launch-check --details, then a hand check

What this report is, and is not

An automated check of one commit of the code, with every finding checked by hand by a person, at one point in time. It is not a penetration test, a certification, or a guarantee. It does not say the app is secure. Anything not listed under "What was checked" was not looked at.

Summary

A signed-out visitor who has a post's id can delete or rewrite that post. Furnace found it, and reading the code confirms it. Fix it before anything else: it is a few lines of code (below). Everything else is an improvement, not a blocker.

ConfirmedLikelyQuestions for you
100

What the hand check changed (this is what you pay a person for)

Findings

F1 · High (Critical if post ids are ever visible to others) · Confirmed

Anyone with a post's id, signed in or not, can delete or rewrite it

Where
The post API's delete and update handlers, through the shared "does this user own this post?" helper.
What happens
The helper counts posts matching the post's id and the signed-in user's id. With nobody signed in, the user id is undefined, and Prisma leaves out a filter whose value is undefined. The check then matches the post by its id alone and says "yes". So a request with no login can delete any post, or replace its title and content. The attacker needs the post's id. Ids are long random strings, so they are hard to guess, but they show up in links, browser history, screenshots and logs. An id is not a password, and the code treats it as one.
How I checked
Read the handlers and the helper. A signed-in user asking for another user's post is refused correctly (the filter uses their own id and finds nothing); only the signed-out case slips through. Prisma's documented behaviour for undefined in a filter is to ignore that field.
Fix
Refuse when there is no session, before the query, and use the non-optional id. Both handlers already turn false into a 403.
async function verifyAccess(postId: string) {
  const session = await getServerSession(authOptions)
  if (!session?.user?.id) return false
  const count = await db.post.count({
    where: { id: postId, authorId: session.user.id },
  })
  return count > 0
}
Check it's fixed
Signed out, send a delete request for a real post id: it must answer 403 and the post must still exist. Signed in as another user: also 403.

Worth improving (does not block launch)

I1. No security headers in the code or host config · Medium

The Next.js config sets none. Your host may add some; check the live site's response headers. Add Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options and Referrer-Policy in the config's headers() (Furnace's report has the code).

I2. No rate limit on the post write endpoints · Low once F1 is fixed

After the fix they need a login. Before it they are open to everyone, which makes F1 worse: a script could empty the posts table quickly.

I3. No rate limit on the billing endpoint · Low

It needs a login and creates a Stripe checkout or billing-portal session per call. Stripe sessions cost nothing, but a signed-in script can create many; a per-user limit (10 a minute) is enough.

Questions for you

None open. (Furnace asked two; both are answered by F1.)

What was checked

What was not checked

The live site, hosting and network settings, the database itself, Stripe and email account settings, the 14 server pages Furnace lists as "not checked for login" (who may see each dashboard page was not reviewed one by one in this sample), packages with known weaknesses (not run for this sample), and anything newer than the audited commit.

What I did to keep this honest

Re-check and signature

Re-check: a paid audit includes one free re-check within 14 days of delivery. You send the new commit when you have fixed things, and each finding is marked Closed, Still open or Changed, with how it was checked.

Signature: in a paid audit, the report's fingerprint is signed with Furnace (Ed25519) and sent with the signature file and the public key, so you can check it has not changed since it was signed. This sample is not signed.

Want this for your app?

Founding price: $99 for the first 10 audits, then $299. Paid before the audit starts. If it finds nothing you need to fix before launch, you get your money back.