Inspiration

We watched a friend build a working MVP in an afternoon with an AI app generator. No engineering background, one prompt and a deploy button. Then we opened the source and found a database key with full write access sitting in the browser bundle. Anyone could have copied it.

Lovable, v0, Bolt, and Replit Agent optimize for "it runs" rather than "it's safe." They ship the same holes over and over: exposed keys, disabled row level security, debug routes left open. A founder using them finds out when someone breaches the app. We built Salus to find out first.

What it does

You sign in, paste your app's URL, and confirm you own it. Salus then runs six checks against the running app:

  • Generator fingerprint. It matches your app against Lovable, Bolt, Replit, v0.dev, and Base44, then looks for that builder's known defaults instead of a gener bundles for keys.
  • Backend defaults. It probes your Supabase tables the way an attacker would, checking whether row level security is on.
  • Source maps. Shipped maps hand over your original source.
  • Admin and debug routes. Paths the generator scaffolded and nobody removed.
  • Security headers. Missing CSP, missing HSTS, missing nosniff, and the wildcard CORS policy that lets any site read your logged in responses.

Three rules govern what lands on the report. A check that ran and found nothing gets deleted rather than written, so a finding means Salus found something. A check that could not run gets marked blocked with its evidence left empty, so the report never describes work that did not happen. Any credential Salus finds gets redacted before storage: the first six and last four characters survive and the middle is masked.

Ownership verification gates the whole thing. The database records your attestation on the same row as the scan, at the moment the scan is created. Salus is not for pointing at strangers' sites.

How we built it

holds auth and Postgres, and every table is scoped by row level security, so the publishable key in our config reaches only the rows belonging to the signed in account.

The scan engine runs as a Deno edge function rather than in the browser, and that was forced on us. A browser cannot read a cross-origin response, so a scanner living entirely in the page can see almost nothing about your app. The function reads the caller's JWT and builds its Supabase client from that token, which means it can only ever touch the calling user's rows. No service role key exists anywhere in the repository.

Supabase Auth handles the OAuth secret exchange for Google and GitHub, so both buttons call the same function. The work went into the failure mode. If a provider is turned off in the project, the sign-in call navigates away before it can return an error and strands the user on a raw JSON 400 page with no way back. We ask the project which providers are live before rendering the buttons.

Design took real iteration. We started with a dense multi-section product page and cut it down until it showed only what a founder needs to decide whether to scan, then rebuilt it around a pastel palette with drifting background shapes, a cursor-following spotlight, and glow on the interactive elements.

Challenges

Scope. We planned four things: generator fingerprinting, an authenticated crawler building an object graph across two test accounts, a classifier separating real leaks from data that is shared on purpose, and an agent that patches the code. Shipping all four in a weekend would have produced a demo that worked on the one app we rehearsed. We shipped the passive tier and made the other two visibly locked instead of quietly broken.

Failure states cost us more time than the checks did. An empty report looks exactly like a clean bill of health, which is the worst possible bug in a security tool. If our tables are missing, the page names that. If the engine never answers, the page says so rather than rendering zero findings.

What we're proud

Ownership verification is part of the core interaction rather than a checkbox in a terms page. The attestation timestamp is a column on the scan row because the modal that collects it is the same action that creates the scan.

The honesty rules matter more to us than the check count. A scanner that invents findings, or that stays silent about checks it could not run, is worse than no scanner, because the founder walks away believing something false.

What we learned

The defensible part of a security product is knowing who you build for. Corgea, Mobb, and Pixee already automate vulnerability fixing for professional teams. Those tools assume you know what RLS is and that you understand your database The defensible part of a security product is knowing who you build for. Corgea, Mobb, and Pixee already automate vulnerability fixing for professional teams. Those tools assume you know what RLS is and that you understand your database key is public. Our user does not, and that gap is the product.

We also learned where the line falls between what a page can do alone and what needs a server. Cross-origin is the wall. Everything interesting about scanning someone's live app sits on the far side of it.

What's next

Tier 2 is the authenticated deep scan: sign in as two test accounts and check whether one can read the other's private data. Broken object level access is the most common serious vulnerability on the web and the hardest to spot by reading code.

Tier 3 proposes a patch for a confirmed finding, then re-runs the same attack to prove it fails, without breaking the app for real users. We treat that as optional. When someone trusts you with their app's security, reliability beats flash.

Built With

Share this project:

Updates

Submission history