The risk is the deploy after the one you checked
The concrete launch risk here is not the launch. It is deploy number fourteen. You ran a careful pass before going public: source maps off, security headers set, CORS scoped to your own origin, no secret-shaped strings in the bundle, the storage bucket private. That was true on launch day. The question this note answers is a pre-launch security process for developers that keeps it true on every deploy after.
Public-surface controls drift because they live in config that an unrelated change can touch. A teammate or an AI coding assistant edits the build config to fix a sourcemap-debugging problem and flips source maps back on. A "make the API work from the new domain" request widens an allowed-origins list to a wildcard. A bucket gets toggled public to share one file and never gets toggled back. None of these break the app, so none of them show up in a normal test run. The app keeps working while the public surface quietly reopens.
Separate what is observable from what is only possible. What is observable, and therefore checkable from outside on every deploy, is the public surface: response headers, whether .map files load, what an anonymous request to your data endpoints returns, the presence of secret-shaped strings in the served JS, and CORS response headers. What is only possible without a repeatable check is that a future change silently undoes one of them and nobody notices until the URL has spread.
Why AI-built apps regress more
AI coding workflows make this worse for two structural reasons, neither of which is about the model writing bad code.
The first is edit velocity with no review loop. The whole appeal of Cursor, Lovable, Bolt, and similar tools is that you describe a change and it lands in minutes, then redeploys. A one-time pre-launch checklist assumes the app is roughly frozen after you check it. That assumption is wrong for an app that ships several times a week from natural-language prompts. The control you verified on Monday is one prompt away from being reverted on Thursday.
The second is that the generated defaults you fixed are the defaults the tool will regenerate. If the framework default is source maps on, or a starter config ships open CORS, then any regeneration of that config tends back toward the insecure default unless your fix is pinned and checked. Fixing it once does not change what the tool produces next time.
- A config edit to debug one issue re-enables source maps for the whole production build.
- A new third-party integration prompts a copy-pasted CORS block that widens the allowed origin.
- A schema or table change resets Row Level Security expectations, so a table that was scoped becomes readable by the anon role again.
- A dependency bump or scaffold regenerates a header config and drops a header you had added by hand.
Wire the public-surface checks into a gate
The habit is to run the same public-surface checks on a schedule the app cannot escape: a pre-deploy gate, a post-deploy smoke check against the live URL, or both. The point is that a regression fails loudly instead of sitting silent. These are reproducible self-checks against your OWN app, run from a clean session with no logged-in cookies.
Pick the small set of checks that map to controls you actually set, and assert on them. For each, define the safe result and the suspicious result so the gate has a clear pass or fail.
A practical way to run these is to script the requests in your CI pipeline against a preview or production URL and fail the job on any suspicious result, the same way a failing test fails the build. The gate does not need to be clever. It needs to run every time and break the deploy when a control disappears.
- Source maps: request the built JS, then request the same path with .map appended. Safe result: the .map URL returns 404. Suspicious result: it returns 200 with readable original source. See the per-framework disable guides for how to make this stick in Vite, Next.js, or Nuxt builds.
- Security headers: read the response headers on your main routes. Safe result: the headers you set on launch day are present (for example Strict-Transport-Security, X-Frame-Options or a CSP frame-ancestors directive, X-Content-Type-Options). Suspicious result: one you previously set is now missing. This is a CWE-693 protection-mechanism-failure class problem: the control was there and silently went away.
- CORS: send a cross-origin style request and read the Access-Control-Allow-Origin response header. Safe result: your own origin or a tight allowlist. Suspicious result: a wildcard, or an origin you do not recognize being reflected back.
- Bundle secrets: fetch the served JS and grep for secret-shaped prefixes as plain strings, for example sk_live_, sk-ant-, sk-proj-, or a service_role JWT shape. Safe result: no match. Suspicious result: any match, which means a key reached the client bundle since the last clean deploy.
- Data endpoints and buckets: make an unauthenticated request to a data endpoint and a storage path that should be private. Safe result: empty or a permission error. Suspicious result: rows or a file come back to the anonymous caller.
What the gate catches, and what it does not
The fix is to make these checks non-optional and to keep them honest about scope. A passing gate is a regression signal, not a proof of correctness.
What the gate reliably catches is the regression of a control that is observable from outside: a header that vanished, source maps that came back, a CORS origin that widened, a bucket that flipped public, a secret-shaped string that appeared in the bundle. Those are binary and stable across deploys, which is exactly what makes them good gate material.
What the gate does not catch is anything that requires reading your code or your logic. It cannot tell you that an authorization check is missing on a route that returns a 200 for the authenticated caller it was tested with. It cannot confirm a Stripe webhook verifies its signature, that entitlement is enforced server-side, or that a Row Level Security policy actually matches the rows you intend, since a policy of USING (true) passes a from-outside read just as a correct policy would for the owner. It also will not detect account enumeration or timing differences, which are behavioral and need dedicated testing. Distinguish the two: the gate watches authentication-adjacent and exposure controls on the surface; authorization correctness and business logic stay with manual review. For triaging which findings block a launch versus which are hardening, the triage note covers ordering, and the scan-versus-code-review comparison covers where each method's coverage ends.
Where VibeCodeGuard fits
VibeCodeGuard's Launch Check is the automated scan step you drop into this habit loop. It runs the public-surface checks from a clean external session, so each deploy gets a fast signal that the last change did not regress headers, re-expose source maps, widen CORS, leak a secret-shaped string into the bundle, or reopen a public bucket or readable endpoint. Run it as the recurring step rather than re-deriving the checks by hand every time, and triage anything it flags by severity before the URL goes anywhere.
A clean scan means the public-surface controls you set are still in place on this deploy. That is the specific thing it proves, and the reason it is worth running on a schedule rather than once.
Next step: pick the three or four controls you set before launch, write the from-outside check for each, and put them where they run on every deploy. Then run a focused Launch Check against the live URL after each ship, so a dropped control shows up in your gate and not in someone else's scan of your app.
A clean Launch Check does not prove the app is secure. It proves the externally observable controls did not regress since the last clean deploy. It does not read your authorization logic, verify webhook signatures, confirm RLS policy intent, or test for enumeration. Pair the recurring scan with manual code review for anything behind the login.
> launch check
Scan the public surface before launch.
Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.