Is my Claude Code app secure? A Claude Code app security checklist before you launch
Short answer: Not automatically. Claude Code helps you build the app, but the live app is only as secure as the code, keys, and database settings it ships with. This checklist covers the deployed web app only, not your local Claude Code session.
Ember can help check what your published app shows a visitor. Run a free SniffPup scan of your own site. Passive checks only, for sites you own; a clean scan is not proof of security.
Home › Guides › Is my Claude Code app secure? · Last updated · By Ember, SniffPup
1. Start with the live site, signed out
Open your own deployed URL in a private window, signed out. This checklist is about that live web app. It is not an audit of your local Claude Code session, your laptop, or your source repository. Try the pages that should need a login. Then use two test accounts you created, on a site and data you own: sign in as the first and try to open the second test account's record by changing an id in the URL. You should be refused both times. Do not test anyone else's account or app. Hiding a button does not protect data. The check has to run on the server or in the database. Anthropic's Claude Code security docs say you are responsible for reviewing proposed code and commands for safety before you approve them. Review generated routes that touch sign-in, payments, or data access before you ship, including any placeholder that skips auth.
2. Search the served JavaScript for secret-key patterns
On your own deployed site, open DevTools and search the served JavaScript for patterns such as sk-, sk_live_, sb_secret_, and the string service_role. Treat every match as a lead to verify, not proof that a live secret leaked. A prefix can match a test key, a revoked key, a placeholder, an example, or a publishable key that is public by design. The string service_role on its own is a name. The legacy Supabase service_role value is a JWT, and the current Supabase secret key starts with sb_secret_. Confirm the provider, whether the value is live or test, publishable or secret, and whether a full value is present. Do not use the key. If a real secret shipped in the bundle, create a new key at the provider, move the call to server code, redeploy, then revoke the old key. Deleting it from the code is not enough, because the old bundle may already have been downloaded.
3. Keep public-by-design variables public-only
Vite's docs say variables prefixed with VITE_ are exposed in client-side source after bundling, and that VITE_ variables should not contain secrets such as API keys. Next.js docs say non-NEXT_PUBLIC_ variables are only available in the Node.js environment, and that only a NEXT_PUBLIC_ prefix inlines a value into the JavaScript bundle sent to the browser. A client bundle that reads an unprefixed variable directly gets an empty string, not the secret. That is not a full seal: props passed from a Server Component to a Client Component are serialized into the payload the browser receives, so passing the value onward can still leak it, and so can putting it in a response. VITE_ and NEXT_PUBLIC_ are fine for public values, such as a Supabase project URL, a Supabase publishable or legacy anon key, or a Stripe publishable pk_live_ key. They are not fine for a Supabase secret or service_role key, a Stripe sk_live_ key, or an AI provider secret. Read secrets only in server code, and return the result, not the key. If you remove a public prefix, rebuild and redeploy. The old bundle keeps the old value until you do.
4. Check /.env and /.git/config by content
Visit /.env and /.git/config on your own live site. You should never see file contents. Do not trust the status code alone. Netlify documents an SPA rewrite that serves index.html with status 200 for every requested path, so a 200 on /.env may be your app shell, not an exposed file. Check the body and the content type. A 404 on those two paths does not clear your JavaScript bundle, source maps, or other served files.
5. Enable Supabase RLS and write owner policies
If the app uses Supabase, enable Row Level Security on every user-data table in an exposed schema. Supabase's docs say that once RLS is enabled, no data is accessible through the API with a publishable key until you create policies, and that grants and policies are separate steps. Grant only what each role needs, and add a separate owner-scoped policy for select, insert, update, and delete. Test signed out, and as two test accounts you own: one account must not read or change the other test account's rows. Do this only on your own project and your own data. Enabling RLS alone is not enough. A leftover policy such as using (true) can still expose rows, because permissive policies combine with OR. Check views too. Supabase's docs say views bypass RLS by default because they are usually created with the postgres user, and Postgres creates them as security definer, so the view reads tables as its owner. On Supabase that owner is usually postgres, which has bypassrls, so the view can return rows the table policies hide. A view owned by a role without bypassrls does not skip RLS the same way. On Postgres 15 and above, set security_invoker = true so the view obeys the querying user's policies. Keep secret keys server-only. A secret key authorizes the service_role role, which has bypassrls, and must never be used in the browser. Supabase also notes that a secret key bypasses RLS only when the request carries no user access token. A passive scan cannot see your policy text. Check it in Supabase, with Security Advisor and your own tests.
6. Treat CLAUDE.md as context, not a control
A line in CLAUDE.md such as "never expose keys" can guide a building session. It does not enforce anything in the shipped app. Anthropic's CLAUDE.md docs say Claude treats those files as context, not enforced configuration, and that to block an action regardless of what Claude decides you should use a PreToolUse hook. The permissions docs say permission rules are enforced by Claude Code, not by the model: instructions in a prompt or in CLAUDE.md shape what Claude tries to do, but they do not change what Claude Code allows. Rules are deny, ask, and allow, and deny is checked first. Do not count a CLAUDE.md rule as a fix. Check the live bundle, the database, and protected routes on the server. Keep real secrets out of CLAUDE.md and any config you commit. That check is in your own repo, not something an outside site scan can see. Anthropic encourages MCP servers you write yourself or from providers you trust, and says it does not security-audit or manage any MCP server. That is about your building setup, not the live site.
7. Run a passive check of a site you own
Run a free SniffPup scan of a site you own. It loads the page and the JavaScript it ships, the way a visitor's browser would, plus a short fixed list of well-known paths such as /.env and /.git/config. It is passive and read-only: it does not log in, send attack traffic, or use a secret key it finds. It cannot see your source repo, your RLS policy text, your server logic, anything behind a login, or your local Claude Code session. SniffPup's scoring page says a high score means no issues were found in the checks it runs, not that the app is secure. Use that outside check with the owner-side checks above, not instead of them.
Next: Leaked Supabase service_role key: what to do, Find exposed API keys on your website and Is my Cursor app secure?.
FAQ
Is my Claude Code app secure?
Not automatically. On a site you own, open the deployed app logged out and search the live JavaScript for secret-key patterns. A prefix match is a lead to verify, not proof. Check /.env and /.git/config by content as well as status, because an SPA can return 200 with index.html. If you use Supabase, enable RLS with owner policies on user-data tables. A view usually runs as its owner, often postgres, and can skip RLS until you set security_invoker. Keep secret and service_role keys in server code. A free SniffPup scan of a site you own looks for keys and open files the frontend serves. It does not audit your local Claude Code session, source repo, or RLS policy text.
Does a CLAUDE.md rule keep secrets out of my app?
No. Anthropic's docs load CLAUDE.md as context, not as enforcement. A line there does not stop a secret from landing in a VITE_ or NEXT_PUBLIC_ variable, or from being passed into a client prop or response. Permission rules and a PreToolUse hook are the controls on the building session. Verify on the live site, and keep secrets in server-only code.
I found sk- or service_role in my JavaScript. Is that a leaked secret?
Not necessarily. A prefix match is a lead to verify, not proof a live secret leaked. It may be a test key, a revoked key, a placeholder, a publishable key, or, for service_role, only a variable name. Confirm the provider, whether the key is live and secret, and whether a full value is present. If it is a real live secret, rotate it at the provider, move the call server-side, and redeploy.
Can SniffPup audit my Claude Code session or my Supabase policies?
No. A SniffPup site scan is a passive outside check of a live site you own: keys and open files the frontend serves. It cannot see your local Claude Code session, your source repo, your server logic, or your RLS policy text. Check policies in Supabase, and session and repo settings on your own machine. Test signed-in access only with test accounts and data you own.
Sources: Claude Code security, Claude Code permissions, CLAUDE.md, Vite env and mode, Next.js environment variables, Next.js server and client components, Supabase Row Level Security, Supabase API keys, Netlify rewrites and proxies, and SniffPup scoring, checked November 5, 2026.