What you get for $39, shown on an app we broke on purpose
When we look at any security tool, the first thing we want to know is what we’d actually get for the money. A landing page can’t really tell you that, so here’s the whole thing, start to finish: the free summary, the paid report, the fixes, the re-scan, and the certificate at the end.
The cards use the same components as a real report, so this is what yours will look like. The grey boxes between them are our notes for this page. Your report won’t have those, but every finding in it comes with its own plain-English summary and a fix prompt.
1. Before you pay: the free summary
This is everything the free scan shows. A score out of 100, how many findings landed at each level, and the worst one in plain words. We show the worst one for free on purpose. If there’s a live key in your JavaScript, you should know today, whether you buy anything from us or not.
How the score works: everyone starts at 100. Each critical finding costs 25 points, a high 12, a medium 5 and a low 2. A score is a rough signal, not the verdict. The verdict is simpler: any critical or high finding means the app isn’t ready, and that’s also the rule the badge uses.
2. After you pay: the full report
Paying $39 opens every finding. Each card has what a stranger can see, with values redacted, and a fix prompt you can paste into Lovable, Bolt, v0, Replit or Cursor. Because this test app has no builder signature, its prompts use the generic wording. Yours will name your builder if we can tell which one you used.
criticalOpenAI API key in client code
What it means
The app calls OpenAI straight from the browser, so the key has to travel with the page. In this test app it sat in a NEXT_PUBLIC_ variable, and Next.js copies anything with that prefix into the JavaScript it sends to every visitor. Anyone who opens the browser’s developer tools can read it.
Why it matters
Whoever has the key can spend your OpenAI credit, and it keeps working until you revoke it. Nobody needs to be clever for this one. They copy a string. This is the one we’d want to hear about on day one, which is why the free summary shows it.
What we’d do
Revoke the key first, before you touch any code, because the old value is already out. Then move the call into a server route and give the new key a name without NEXT_PUBLIC_ or VITE_. Notice the proof only shows the first and last few characters. We never store the whole key.
highUnauthenticated response from /api/notes
What it means
/api/notes handed back every note in the app to a request that had no login at all. Our scanner found that address inside the app’s own JavaScript and asked for it once, the way a curious stranger would.
Why it matters
Here it’s two fake notes from “alice” and “bob”. In a real app it would be your users’ data, and the address is sitting in your JavaScript for anyone to find.
What we’d do
Check for a session at the top of the route and send back a 401 when there isn’t one. Then make sure the query only returns rows that belong to the person asking. The proof shows the shape of the response, never the values.
mediumNo Content-Security-Policy
What it means
The page came back with no Content-Security-Policy, so the browser has no rule about which scripts are allowed to run.
Why it matters
On its own this isn’t a hole. It’s the seatbelt. If something else goes wrong and a stranger’s script gets onto the page, a policy limits what that script can reach.
What we’d do
The fix prompt includes a header block you can paste. Load your pages after you add it, because a strict policy can block a script you actually need. On the fixed copy of this app we put the headers in next.config.
mediumPage can be framed
What it means
Nothing tells the browser to refuse when another site tries to load this app inside a frame.
Why it matters
That’s what clickjacking needs: someone shows your real page under their own buttons and gets your users to click things they can’t see. It matters most on pages that take an action, like settings or payments.
What we’d do
One header fixes it: X-Frame-Options: DENY, or frame-ancestors 'none' inside the CSP. It’s in the same header block.
mediumProduction source map is public× 5
What it means
Source maps let anyone rebuild your original code from the live site, with your file names and comments. This test app had productionBrowserSourceMaps switched on, so the scanner found a map behind most of its script files. The real report gives each file its own card, which is why there are several.
Why it matters
Your code isn’t a secret in the way a key is, but readable source makes the next mistake much easier to find. The scanner also searched the maps for keys, then threw their contents away.
What we’d do
Switch production source maps off. If your error tracker needs them, upload them to it privately instead of serving them to everyone.
The other 4 cards for this one, exactly as the report lists them
lowNo X-Content-Type-Options
What it means
The response didn’t send X-Content-Type-Options: nosniff.
Why it matters
Without it, a browser may guess that a file is a script and run it. It’s a small one.
What we’d do
Add the header. It takes a minute, and it’s in the same block as the others.
lowNo Referrer-Policy
What it means
The response didn’t send a Referrer-Policy.
Why it matters
When a user clicks a link to another site, the browser can send along the full address they came from, including anything in the query string, like a reset token.
What we’d do
strict-origin-when-cross-origin is a sensible default. Same header block again.
Checks that stayed quiet
Not every line is bad news. The report also lists what we looked at that came back fine, so you know it was checked and not skipped.
Local target is not using HTTPS
This loopback URL is HTTP, which is normal for a local fixture. A public launch still has to use HTTPS.
Next.js 15.5.28 is not in the advisories LockShip checks
LockShip compared the live version with GHSA-2464-fhr4-w9xw, GHSA-9qr9-h5gf-34mp, GHSA-f82v-jwr5-mffw. New advisories keep coming, so keep an eye on the Next.js blog.
No hosted-builder signature
No Lovable, Bolt, v0, Replit, or Cursor signature was found. Fix prompts use generic builder wording.
X-Powered-By says Next.js
Every response tells visitors the site runs on Next.js. There is no version in it, and your scripts give the framework away anyway, so this is a small hint, not a problem. Next.js turns it off with poweredByHeader: false in next.config.
One of those only shows up because the test app ran on our own machine. On a real public site, plain HTTP is a high finding, and the scan also checks your TLS certificate and HSTS, which a local app can’t show.
What the scan did not test
Every scan is read-only. It never signs in and never sends anything but GET and HEAD, so some things are off limits by design. When the scan sees one of them, for example tables it could only test by writing to them, or Server Actions it would have to call for real, the report lists it under “What this scan did not test” and links to a way to check it yourself. This test app didn’t trigger any, so the list is empty here.
If your app uses Supabase
The notes app above doesn’t use a database we can probe, so it can’t show the finding LockShip was built around: a Supabase table that strangers can read with the public key. The key itself is meant to be public. The problem is a table without Row Level Security. This is the story behind CVE-2025-48757, and it’s where the report earns its money, because it hands you both the prompt for your builder and the SQL.
Read the SQL before you run it. It assumes the column that says who owns a row is called user_id, and it says so in its first line. If yours is called something else, change it. Our Supabase RLS checklist walks through the rest by hand.
3. Fix, re-scan, badge
Next we did what the report said, on a copy of the same test app at the same address:
Took the OpenAI key out of the browser. The call now goes through a server route that checks the login and reads the key from a server-only variable.
Made /api/notes require a session and return only your own notes.
Switched production source maps off.
Added the Content-Security-Policy, X-Frame-Options, nosniff and Referrer-Policy headers in next.config, and turned off X-Powered-By while we were in there.
Then we pressed re-scan. This is what came back:
Re-scans are included for 7 days after you buy the certificate. The badge is only ever issued by a re-scan after payment, never by the first scan, so it always reflects a check you ran after fixing things. This one passed, so the certificate below went live.
4. The certificate
Launch Safe certificate · example
127.0.0.1
Status: Verified
Issued
18 Oct 2026
Last checked
18 Oct 2026
Score
100 / 100
Monitor
Not monitored
This is a point-in-time, best-effort, read-only review. It is not a penetration test and it is not a guarantee the app is safe to launch. Disclaimer.
How someone checks it
Every certificate gets its own public page on lockship.dev, at an address nobody can guess. The badge on your site links to it.
The badge isn’t a picture you download and keep. Your site loads it from LockShip each time, drawn from the current state of your scan, and it prints the hostname it was issued for. A visitor can click it and see that it belongs to the site they’re on.
With Monitor ($9/month), we re-scan about once a week. If a later scan finds a critical or high issue, the badge turns grey and says “Check failed” until a re-scan passes again. If Monitor ends, it switches to “Expired” once the paid month runs out.
Without Monitor, the badge stays as it was at your last passing re-scan, and the certificate page shows the date of that check and “Not monitored”. We’d rather show the date plainly than suggest we’re still watching when we aren’t.
You also get the whole report as a PDF to keep or forward. And the line on the certificate is meant seriously: this is a point-in-time, read-only check from the outside. It isn’t a penetration test and it can miss things. The disclaimer has the details.
Want to see yours?
The free scan gives you your score and the worst thing it finds, like section 1 above. You prove you own the domain first, so nobody can point it at a site that isn’t theirs. Pay $39 only if you want the rest.