<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Novruz Veliev</title>
    <description>The latest articles on DEV Community by Novruz Veliev (@novruz_veliev).</description>
    <link>https://dev.to/novruz_veliev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4132221%2Fe7b160c1-7e58-4687-a80b-541cfe0699f4.jpg</url>
      <title>DEV Community: Novruz Veliev</title>
      <link>https://dev.to/novruz_veliev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/novruz_veliev"/>
    <language>en</language>
    <item>
      <title>Lovable Deep Scan vs a manual check: what the scan finds and what it can't see</title>
      <dc:creator>Novruz Veliev</dc:creator>
      <pubDate>Mon, 12 Oct 2026 09:15:29 +0000</pubDate>
      <link>https://dev.to/novruz_veliev/lovable-deep-scan-vs-a-manual-check-what-the-scan-finds-and-what-it-cant-see-18a0</link>
      <guid>https://dev.to/novruz_veliev/lovable-deep-scan-vs-a-manual-check-what-the-scan-finds-and-what-it-cant-see-18a0</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://shipclarity.app/lovable-deep-scan-vs-manual-check.html?r=devto" rel="noopener noreferrer"&gt;shipclarity.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Lovable now ships with its own security scanner. A quick scan runs on every publish, and a Deep scan reviews your whole codebase when you ask for it. Founders often ask me whether that makes a manual check pointless. Short answer: run the scan first, it's free and it's good. Then look at the list below of what it was never built to see.&lt;/p&gt;

&lt;p&gt;Everything I say about Lovable's feature here comes from its own documentation and blog, as of October 2026. Lovable changes fast, so if a detail below looks out of date, the docs win.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Lovable's scans are
&lt;/h2&gt;

&lt;p&gt;There are two built-in scans. Both are free to run and don't use credits.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Quick scan&lt;/strong&gt; runs by itself every time you publish and takes about 10 to 15 seconds. It reads your database access rules and settings, including Row Level Security, and checks your npm packages against known vulnerabilities. Only packages rated critical become findings.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Deep scan&lt;/strong&gt; does not run by itself. You start it from the security view, and it takes a few minutes. It is an AI review of all your application code. Lovable's docs list what it looks at: access control and authorization, unauthenticated endpoints, unsafe input and injection, leaked secrets, payments and billing, authentication and account security, and exposed personal data. Lovable suggests running it before a public launch, when you handle sensitive data, or after big changes.&lt;/p&gt;

&lt;p&gt;Findings come in three levels: Critical, Warning and Info. Some findings can be fixed with one click. Deep scan findings are not auto-fixed, so you fix those yourself or ask Lovable in chat, which costs credits.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Lovable's own docs say these tools find common security issues but "cannot guarantee complete security". That is a fair description, and worth keeping in mind when the scan comes back clean.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the scan is good at
&lt;/h2&gt;

&lt;p&gt;Credit where it's due. A table with RLS switched off, a policy that lets anyone read everything, a secret key pasted into frontend code, a package with a known critical hole: these are exactly the mistakes AI builders make, and the scan catches them in seconds. It also never gets tired. It reads every file, every time.&lt;/p&gt;

&lt;p&gt;The Deep scan goes further than most founders expect. It reads your code with your app in mind, so it can flag an Edge Function that anyone can call without logging in, or a price that comes from the browser and could be changed by the buyer. A human reviewer reading the same code would need hours to do that.&lt;/p&gt;

&lt;p&gt;If you have never run it, stop here and run it. Fix what it flags. Paying anyone to find what a free tool finds in four minutes is a waste of money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it can't see some problems
&lt;/h2&gt;

&lt;p&gt;Both built-in scans read. They read code, database rules and package lists. Lovable's docs describe them as static analysis. They don't open your live app, sign up, wait for an email, or log in as anybody.&lt;/p&gt;

&lt;p&gt;That matters, because a lot of launch bugs don't live in the code at all. They live in the email provider's settings, in the Stripe dashboard, in a redirect URL saved in Supabase, in what your pricing page promises. Code can look right and the app can still fail when a real person uses it.&lt;/p&gt;

&lt;p&gt;To be fair, Lovable also lists optional third-party connectors, including an AI penetration test that sends requests to your running app and tries to break authentication and API flows. That is closer to testing, and worth a look for apps with sensitive data. It is still a security tool. It doesn't ask whether a customer can get through your product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven things only a person with accounts finds
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The sign-up email that never arrives
&lt;/h3&gt;

&lt;p&gt;The code calls the email function correctly. The scan is happy. Meanwhile the sending domain isn't verified, or the default sender has hit its limit, and new users sit waiting for a confirmation link. You only see it by signing up with a fresh inbox and waiting. More on this in &lt;a href="https://shipclarity.app/lovable-password-reset-email-not-arriving.html?r=devto" rel="noopener noreferrer"&gt;why Lovable reset emails don't arrive&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The password reset that leads nowhere
&lt;/h3&gt;

&lt;p&gt;Even when the reset email does arrive, the link often opens an old preview address or a page that doesn't handle the token. The user clicks, sees an error, and gives up. Nothing about this is a security flaw, so no security scan will report it. It still loses you customers.&lt;/p&gt;

&lt;h3&gt;
  
  
  One user seeing another user's data
&lt;/h3&gt;

&lt;p&gt;The scan checks that RLS policies exist and aren't wide open. It can't tell you whether the policy matches what you meant. A policy can be valid and still let a team member see every team, or let a user read rows through a view nobody thought about. The way to know is practical: two accounts, one creates data, the other tries to reach it through the screens, the URL and the API. The do-it-yourself version is in &lt;a href="https://shipclarity.app/supabase-rls-anon-key-check.html?r=devto" rel="noopener noreferrer"&gt;Supabase RLS and the anon key&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test keys on the live site
&lt;/h3&gt;

&lt;p&gt;The Deep scan reviews payment logic in your code. It doesn't see which Stripe mode your live site actually runs in, whether the live webhook exists, or whether the price IDs in your secrets belong to test mode. Those settings live in dashboards, outside the codebase. The 15-minute check is in &lt;a href="https://shipclarity.app/stripe-test-mode-vs-live-mode.html?r=devto" rel="noopener noreferrer"&gt;Stripe test mode vs live mode&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The pricing page that disagrees with the terms
&lt;/h3&gt;

&lt;p&gt;Your pricing page says "cancel anytime" and the terms say annual plans aren't refundable. The plan card says 5 projects and the app stops you at 3. This is a mismatch between words on two pages and behaviour in a third, and it only shows up when someone reads them side by side and then tries the limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  The layout on a real phone
&lt;/h3&gt;

&lt;p&gt;A button hidden under the keyboard, a modal you can't close on a small screen, a table that scrolls the whole page sideways. Code review can't predict how Safari on an iPhone renders your page. Someone has to hold the phone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Broken paths through the app
&lt;/h3&gt;

&lt;p&gt;A link to a page that was renamed two prompts ago. A Back button that drops you on a blank screen. An onboarding step that only works if you click in the expected order. Each piece of code is fine on its own. The path between them is broken, and a path only exists when someone walks it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order that makes sense
&lt;/h2&gt;

&lt;p&gt;Run the Quick scan on every publish. It happens anyway, so read what it says instead of clicking past it. Run the Deep scan before launch and after big changes, and fix what it flags.&lt;/p&gt;

&lt;p&gt;Then spend an hour as your own first customer. Use a new email address. Sign up, reset the password, pay, and try to open data that belongs to a second account. Do it once on your phone. You'll find some of the gaps above yourself, and that hour is worth more than any tool.&lt;/p&gt;

&lt;p&gt;For a sense of what shows up from the outside alone, I scanned &lt;a href="https://shipclarity.app/lovable-apps-what-breaks.html?r=devto" rel="noopener noreferrer"&gt;190 live Lovable apps&lt;/a&gt; without logging in. Behind the login there is more, and that part needs accounts.&lt;/p&gt;

&lt;p&gt;If you'd rather have someone else be the first customer, that manual pass is what I do at &lt;a href="https://shipclarity.app/?r=devto" rel="noopener noreferrer"&gt;ShipClarity&lt;/a&gt;: real inboxes, two accounts, test payments, real phones. Run Lovable's scans first. I start where they stop.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>lovable</category>
      <category>testing</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Stripe test mode vs live mode: a 15-minute check before your first real payment</title>
      <dc:creator>Novruz Veliev</dc:creator>
      <pubDate>Mon, 05 Oct 2026 09:01:15 +0000</pubDate>
      <link>https://dev.to/novruz_veliev/stripe-test-mode-vs-live-mode-a-15-minute-check-before-your-first-real-payment-p6</link>
      <guid>https://dev.to/novruz_veliev/stripe-test-mode-vs-live-mode-a-15-minute-check-before-your-first-real-payment-p6</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://shipclarity.app/stripe-test-mode-vs-live-mode.html?r=devto" rel="noopener noreferrer"&gt;shipclarity.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Stripe gives you two copies of everything: one for testing, one for real money. Keys, products, prices, webhooks, payment links and the customer portal all exist twice. Most launch-day payment bugs I see in Lovable and Bolt apps come from one piece still pointing at the test copy while the rest has moved to live.&lt;/p&gt;

&lt;p&gt;The symptoms are quiet. Checkout works for you, because you test with the card that always works. A real customer gets an error, or pays and never gets access, and you hear about it days later, if at all. Below is the check I run before an app takes its first real payment. It takes about 15 minutes and one small real purchase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find out which key your live site actually uses
&lt;/h2&gt;

&lt;p&gt;Every Stripe key says which copy it belongs to. Publishable keys start with &lt;code&gt;pk_test_&lt;/code&gt; or &lt;code&gt;pk_live_&lt;/code&gt;, secret keys with &lt;code&gt;sk_test_&lt;/code&gt; or &lt;code&gt;sk_live_&lt;/code&gt;. The publishable key sits in your frontend, the secret key must only live on a server - in a Lovable or Bolt app that usually means Supabase Edge Function secrets or your hosting's environment variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to check
&lt;/h3&gt;

&lt;p&gt;Open your live site, then DevTools → Sources, and search the loaded JavaScript for &lt;code&gt;pk_test_&lt;/code&gt;. If it's there, your checkout still starts in test mode. While you're in there, search for &lt;code&gt;sk_&lt;/code&gt; too: a secret key in the browser is a bigger problem than test mode, and it needs rotating in the Stripe dashboard today.&lt;/p&gt;

&lt;p&gt;Then check the server side: in Supabase, Edge Functions → Secrets (or wherever your project keeps them), look at the Stripe secret. It's common to switch the publishable key and forget the secret one, or the other way round. Both have to say live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prices and products don't move to live by themselves
&lt;/h2&gt;

&lt;p&gt;A product or price you created in test mode does not exist in live mode. Its ID (&lt;code&gt;price_...&lt;/code&gt;) is only valid in the copy where it was created. If your code or your AI builder hardcoded test price IDs, live checkout fails with an error like "No such price", even though the key is right.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to check
&lt;/h3&gt;

&lt;p&gt;Stripe dashboard, test mode switched off → Product catalog. Every plan your pricing page sells should be there with the same amount, currency and interval. Then compare the price IDs your app uses (search your code or Edge Functions for &lt;code&gt;price_&lt;/code&gt;) with the IDs shown in live mode. They will be different strings - that's expected - and every one in your code must be the live one.&lt;/p&gt;

&lt;p&gt;Check the amounts while you're there. A $19 plan in test mode and a $190 plan in live mode, or monthly in one and yearly in the other, is a mistake that nobody catches with a test card.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhooks: a separate endpoint and a separate secret
&lt;/h2&gt;

&lt;p&gt;Webhooks are how Stripe tells your app "this person paid". They're set up per mode. A webhook endpoint you added in test mode receives nothing from live payments, and its signing secret (&lt;code&gt;whsec_...&lt;/code&gt;) is different from the live one. The result is the worst kind of bug: the customer is charged, and your app never unlocks their account.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to check
&lt;/h3&gt;

&lt;p&gt;Live mode → Developers → Webhooks. There must be an endpoint pointing at your production URL, listening to the events your app uses (typically &lt;code&gt;checkout.session.completed&lt;/code&gt;, and the subscription events if you sell plans). Copy its signing secret and make sure your server uses that one, not the test one. After the real purchase below, open the endpoint and check that the event shows as delivered, not failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Payment links and the customer portal
&lt;/h2&gt;

&lt;p&gt;If you sell through Stripe Payment Links, look at the link behind every "Buy" button. Test links contain &lt;code&gt;test_&lt;/code&gt; in the address, and the page they open shows a "Test mode" badge. A live site with a test payment link takes no money at all.&lt;/p&gt;

&lt;p&gt;The customer portal - where subscribers update a card or cancel - is also configured separately for each mode. If "Manage subscription" opens an error for a real customer, the portal was only set up in test mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  One real purchase, then a refund
&lt;/h2&gt;

&lt;p&gt;This is the step people skip, and the only one that proves the whole chain. Buy your cheapest plan with a real card, as a new user. Then check four things: the charge appears in the live dashboard, the webhook shows as delivered, your app gave the account what it paid for, and the receipt email arrived (Stripe doesn't send receipts for test payments, so this is the first time you'll see one). Refund yourself from the dashboard when you're done.&lt;/p&gt;

&lt;p&gt;One more quick test: on your live checkout, try the test card 4242 4242 4242 4242. It should be declined. If it goes through, your live checkout is still running in test mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  After the switch: keep testing without touching live
&lt;/h2&gt;

&lt;p&gt;Once you're live, keep test mode (or a Stripe sandbox) for everything you try next, with its own keys in a staging copy of the app. The mistake to avoid is the reverse of launch day: switching the live app back to test keys "for five minutes" to debug something, and forgetting. If you ever do, repeat the key check before you log off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want someone to run the money path with you?
&lt;/h2&gt;

&lt;p&gt;The checks above cover the switch itself. What they don't cover: upgrades and downgrades between plans, failed renewals, coupons, refunds that should remove access, and what a second user sees after the first one pays.&lt;/p&gt;

&lt;p&gt;The payment path, test and live, is one of the manual checks in a &lt;a href="https://shipclarity.app/?r=devto" rel="noopener noreferrer"&gt;Pre-Launch Audit&lt;/a&gt;, done with real test accounts and screen recordings of every step.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>stripe</category>
      <category>testing</category>
      <category>webdev</category>
      <category>saas</category>
    </item>
    <item>
      <title>What breaks in Lovable apps: data from 190 live apps</title>
      <dc:creator>Novruz Veliev</dc:creator>
      <pubDate>Tue, 22 Sep 2026 09:19:18 +0000</pubDate>
      <link>https://dev.to/novruz_veliev/what-breaks-in-lovable-apps-data-from-190-live-apps-5825</link>
      <guid>https://dev.to/novruz_veliev/what-breaks-in-lovable-apps-data-from-190-live-apps-5825</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://shipclarity.app/lovable-apps-what-breaks.html?r=devto" rel="noopener noreferrer"&gt;shipclarity.app&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I ran the same outside-in scan against 190 live apps listed as built with Lovable. No login, nothing a normal visitor's browser could not see. 189 of them had at least one flagged issue. Here is what came up most, what almost never came up, and a two-minute self-check for each.&lt;/p&gt;

&lt;p&gt;Aggregate data only: no app names or domains are published, and none will be. The scan ran on 11 September 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;189 of 190 (99%)&lt;/strong&gt; had at least one flagged issue (average 4.1, median 4, out of 17 checks)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;179 (94%)&lt;/strong&gt; send no Content-Security-Policy header&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;172 (91%)&lt;/strong&gt; send no X-Frame-Options header&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;150 (79%)&lt;/strong&gt; answer 200 OK for a page that does not exist&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;127 (67%)&lt;/strong&gt; have no DMARC record, or one set to &lt;code&gt;p=none&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3&lt;/strong&gt; ship a Google/Firebase API key in the bundle; none shipped a Supabase &lt;code&gt;service_role&lt;/code&gt; key or a Stripe live secret key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0&lt;/strong&gt; exposed a &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;.git&lt;/code&gt; or backup file, and &lt;strong&gt;0 of 125&lt;/strong&gt; probed Supabase projects returned their schema to the public key&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the data was collected, and what it cannot see
&lt;/h2&gt;

&lt;p&gt;The starting point was a public directory of projects built with Lovable: 512 listings. 428 of them were still live, and 395 of those were in English. After removing portfolios, games, agency pages and pages with no product surface (no pricing, payments, or signup), 190 apps were left, all scanned without errors. 155 run on their own domain; 35 are on a platform subdomain such as &lt;code&gt;*.lovable.app&lt;/code&gt;. Some have since moved to Vercel or Netlify, so read this as "built with Lovable", not "hosted by Lovable".&lt;/p&gt;

&lt;p&gt;Each app got the same 17 automatic checks that &lt;a href="https://scan.shipclarity.app/?r=devto" rel="noopener noreferrer"&gt;the free ShipClarity scanner&lt;/a&gt; runs: exposed config and repo files, secret keys in the JS bundle, Supabase schema exposure to the public key, source maps, SPF, DKIM and DMARC, HTTPS and the HTTP redirect, four security headers, the viewport tag, robots.txt and noindex, the status of a missing page, and first-response time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this data cannot tell you
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The scanner never logs in. Signup, password reset, one user reading another user's rows, checkout and refunds are not measured here.&lt;/li&gt;
&lt;li&gt;It does not run JavaScript. It reads the server HTML and up to 8 script files referenced from the home page, so a key in a lazily loaded chunk would be missed.&lt;/li&gt;
&lt;li&gt;Headers are read from the home page only. A CSP set through a &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag is counted as missing.&lt;/li&gt;
&lt;li&gt;DKIM cannot be confirmed from outside (the selector is provider-specific), so there is no DKIM number here.&lt;/li&gt;
&lt;li&gt;The sample is a directory of projects whose makers chose to list them, not a random sample of every Lovable app.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. 189 of 190 had at least one flagged issue
&lt;/h2&gt;

&lt;p&gt;Only one app out of 190 came back clean. The average was 4.1 flagged checks out of 17, the median 4, the worst 8. Most of that count comes from the four items below: configuration defaults nobody revisited, not code bugs. "99% have issues" does not mean 99% are broken for users; it means almost nobody went back over the defaults.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 94% send no Content-Security-Policy
&lt;/h2&gt;

&lt;p&gt;179 of 190 apps send no Content-Security-Policy header. CSP tells the browser which scripts may run on your page. Without it, any injected script (a compromised third-party widget, or unescaped user content) runs with full access to the page, including whatever the logged-in user can see.&lt;/p&gt;

&lt;p&gt;It is a hosting or proxy setting, not app code, which is why it is missing almost everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check yours in 2 minutes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://yourapp.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; content-security-policy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No output means no header (or check DevTools, Network tab, first request, Response Headers). If your host does not let you set headers, a proxy in front of the domain (Cloudflare, or Netlify/Vercel header rules) can add them. Start with &lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt; so a strict policy does not break your own scripts on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. 91% send no X-Frame-Options
&lt;/h2&gt;

&lt;p&gt;172 of 190 apps send no X-Frame-Options header. 171 of them send no CSP either, so there is no &lt;code&gt;frame-ancestors&lt;/code&gt; rule covering the gap. Another site can load the app in an invisible iframe and trick a logged-in user into clicking something real: a delete button, a plan change, a share toggle.&lt;/p&gt;

&lt;p&gt;From the same family: 44 of 190 (23%) lack &lt;code&gt;X-Content-Type-Options: nosniff&lt;/code&gt;, and 23 of 190 (12%) lack HSTS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check yours in 2 minutes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://yourapp.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"x-frame-options|frame-ancestors"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want &lt;code&gt;X-Frame-Options: DENY&lt;/code&gt; (or &lt;code&gt;SAMEORIGIN&lt;/code&gt;) or a CSP with &lt;code&gt;frame-ancestors 'none'&lt;/code&gt;. &lt;code&gt;frame-ancestors&lt;/code&gt; is ignored in a &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; CSP tag; it only works as a response header.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. 79% return 200 for a missing page
&lt;/h2&gt;

&lt;p&gt;150 of 190 apps answered 200 OK when asked for a random URL that does not exist. It is the single-page-app default: the host serves &lt;code&gt;index.html&lt;/code&gt; for every path and the front-end router decides what to show. Visitors may see "not found", but search engines and uptime monitors see a healthy page.&lt;/p&gt;

&lt;p&gt;Search engines may index junk URLs as soft 404s, and monitors keep reporting "up" for deleted routes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check yours in 2 minutes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://yourapp.com/this-page-does-not-exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it prints 200, open the URL in a browser too: a blank page or the home page is worse than a "not found" screen. The minimum fix is a catch-all route that shows a clear not-found page and adds &lt;code&gt;&amp;lt;meta name="robots" content="noindex"&amp;gt;&lt;/code&gt;. A real 404 status needs a host-level rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 67% have no enforcing DMARC
&lt;/h2&gt;

&lt;p&gt;127 of 190 apps have either no DMARC record at all (50) or one set to &lt;code&gt;p=none&lt;/code&gt; (77), which blocks nothing. All 127 are on their own domain: 82% of the 155 custom domains. The 35 on a platform subdomain inherit its record. And 52 custom domains (27% of all apps) have no SPF record either.&lt;/p&gt;

&lt;p&gt;Without an enforcing policy, anyone can send email as your domain, and your own signup and password-reset emails are more likely to land in spam. For the second part there is a separate guide: &lt;a href="https://shipclarity.app/lovable-password-reset-email-not-arriving.html?r=devto" rel="noopener noreferrer"&gt;Lovable password reset email not arriving&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check yours in 2 minutes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig TXT _dmarc.yourdomain.com +short          &lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
nslookup &lt;span class="nt"&gt;-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TXT _dmarc.yourdomain.com      &lt;span class="c"&gt;# Windows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No answer means no record; &lt;code&gt;p=none&lt;/code&gt; is monitor-only. The target is &lt;code&gt;p=quarantine&lt;/code&gt; or &lt;code&gt;p=reject&lt;/code&gt;, but only after SPF and DKIM are set up for the provider that sends your mail, or you will block your own emails.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. 3 apps ship an API key in the bundle
&lt;/h2&gt;

&lt;p&gt;3 of 190 apps had a key matching a known secret pattern in the JavaScript sent to every visitor. All three were Google/Firebase API keys (&lt;code&gt;AIza...&lt;/code&gt;). This type of key is designed to sit in client code and is fine if restricted by HTTP referrer and by API in the Google Cloud console. An unrestricted one can be used on someone else's site at your expense.&lt;/p&gt;

&lt;p&gt;The more useful number is the zero: none of the 190 shipped a Supabase &lt;code&gt;service_role&lt;/code&gt; key, a Stripe live secret key (&lt;code&gt;sk_live_&lt;/code&gt;), or an AWS access key in the scanned bundles, the keys that would hand over the database or the payment account. The Supabase anon key is public by design and was not counted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check yours in 2 minutes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In DevTools on your live app, press Ctrl+Shift+F (Cmd+Option+F on a Mac) and search all loaded files for &lt;code&gt;service_role&lt;/code&gt;, &lt;code&gt;sk_live_&lt;/code&gt;, &lt;code&gt;AKIA&lt;/code&gt; and &lt;code&gt;AIza&lt;/code&gt;. A hit on the first three means rotate the key now and move the call server-side. A hit on &lt;code&gt;AIza&lt;/code&gt; means confirm in Google Cloud Console, Credentials, that the key has both a referrer and an API restriction.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. What held up
&lt;/h2&gt;

&lt;p&gt;The scary items were rare. Out of 190 apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0 exposed a &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;.git/config&lt;/code&gt;, config or backup file at a guessable path.&lt;/li&gt;
&lt;li&gt;190 load over HTTPS. 3 also still answer on plain HTTP without redirecting.&lt;/li&gt;
&lt;li&gt;140 (74%) use Supabase. For 125 of them the scanner could test the public key against the database's REST root, and none returned the schema. The other 15 could not be probed.&lt;/li&gt;
&lt;li&gt;3 publish source maps in production, 3 have the home page set to noindex, and 19 (10%) serve no robots.txt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A closed schema endpoint is a good sign, not a clean bill of health. Whether user A can read user B's rows depends on each table's row-level security, testable only with two real accounts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check yours in 2 minutes&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://yourapp.com/.env
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; http://yourapp.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first should not be 200 with real file contents (a 200 with your normal app page is the soft 404 from point 4, not a leak). The second should be a 301 or 308 redirect to HTTPS.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take from this
&lt;/h2&gt;

&lt;p&gt;Almost everything above is configuration: headers, DNS records, a catch-all route. An afternoon covers it. None of it says whether the app works for a paying user. The failures that cost customers live behind the login: a signup that silently fails, a reset link pointing at the old preview domain, one account seeing another's data, a checkout that charges twice. This dataset cannot say how common those are.&lt;/p&gt;

&lt;p&gt;If you want to see where your own app stands, &lt;a href="https://scan.shipclarity.app/?r=devto" rel="noopener noreferrer"&gt;the free outside scanner&lt;/a&gt; runs the same 17 checks on your URL in about 20 seconds, with no signup.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>testing</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
