The security check
Seven checks read your app's source for the mistakes that actually cause harm — bundled secrets, an open admin route, entitlements the browser can set. Running it is free.
Generated code has a recognisable set of failure modes. A live API key pasted into a file the browser downloads. An admin page with nothing checking who is asking. A "paid" flag stored in the browser where any visitor can set it themselves.
The security check reads your app's actual source for exactly those, and hands you a file, a line and a fix for each.
Open it from the project-name dropdown: Security check.
Running it costs nothing
The check reads files. It does not call a model. It runs automatically when you open the panel, and you can run it as often as you like.
Only the Fix button costs anything, because only that starts a build.
What a clean result looks like
A green tick, the heading No issues found, and:
Checked N files. This catches common mistakes, not everything — review before you take payments.
That caveat is honest, not boilerplate. Seven pattern-based checks are not a security review.
What a finding looks like
Findings are sorted by severity, then by file name. Above them sits a badge row — N high in red, N medium in amber, N low in grey — followed by across N files.
Each finding card has:
- A severity badge — HIGH, MEDIUM or LOW.
- A title, naming the problem and the file.
- A detail, usually naming the line number and what was found there.
- A fix, prefixed
Fix:— what to do about it.
The seven checks
This is the complete list. There are no others.
1. A hardcoded secret in the source
Severity: high when the file is under app/, components/, lib/ or public/ — those are bundled and served to the browser. Medium anywhere else.
The check looks for the shape of a secret, not a variable named "apiKey", because matching names produces a false positive on every line that correctly reads a key from an environment variable. Nine shapes are recognised:
| What it finds | Example prefix |
|---|---|
| An OpenAI key | sk-… |
| An Anthropic key | sk-ant-… |
| A live Stripe secret key | sk_live_… |
| A test Stripe secret key | sk_test_… |
| An AWS access key id | AKIA… |
| A Google API key | AIza… |
| A GitHub token | ghp_, gho_, ghu_, ghs_, ghr_ |
| A private key block | -----BEGIN … PRIVATE KEY----- |
| A hardcoded JWT | eyJ….….… |
When the file is client-side the detail adds: "This file is bundled and served to the browser — treat the key as public and rotate it." That wording is exact and it is not alarmism: anything in a client bundle is readable by anyone who loads your page.
Fix given: Move the value to an environment variable and read it on the server only. Rotate the key you exposed.
2. An environment file shipped with the app
Triggers on any file matching .env at the project root, or any .env file under public/.
Severity: high under public/ — files there are served as-is, so anyone can download it. Low at the root, where the concern is that it travels with your source download.
Fix given: Delete it from the project and keep secrets in the host's environment settings.
3. An admin surface with no access check
Severity: high. Triggers when a file lives under an app/.../admin/ route and contains nothing that looks like an access check — no session lookup, no isAdmin, no requireAuth, no redirect.
This route looks like an admin surface, and nothing in it checks who is asking.
Fix given: Gate it: check for a signed-in admin before rendering, and redirect anyone else.
4. An entitlement stored in the browser
Severity: high. Triggers when the code writes a key containing paid, pro, premium, admin, subscri or entitle to localStorage.
Line N writes a paid/admin flag to localStorage, where any visitor can set it themselves.
This is the single most common way a generated app gives away paid features: browser storage is not a security boundary, and a visitor can change it in two clicks.
Fix given: Decide entitlements on the server from a source the browser cannot write, and treat the client copy as a hint only.
5. Unescaped HTML
Severity: medium. Triggers when the app injects a non-literal value as raw HTML. A hardcoded HTML string is deliberately not flagged; a variable is.
Line N injects a non-literal value as raw HTML, which is how cross-site scripting gets in.
Fix given: Render it as text, or sanitize the HTML before injecting it.
6. Code evaluated at runtime
Severity: medium. Triggers on eval( or new Function(.
Line N runs eval() or new Function(), which executes whatever string reaches it.
Fix given: Replace it with explicit logic; if it parses data, use JSON.parse.
7. A plain-HTTP request
Severity: low. Triggers on a fetch() to an http:// address. Calls to localhost and 127.0.0.1 are exempt.
Line N calls an http:// endpoint, so the request and its response travel unencrypted.
Fix given: Use https:// — browsers also block mixed content on a secure page.
Which files are read
Everything the file tree shows, minus package-lock.json, the development server log and README.md, and only files ending in .js, .jsx, .mjs, .cjs, .ts, .tsx, .css, .json, .html or something containing .env.
The same 500-file ceiling applies as everywhere else. The checked number in the panel is the count of files actually read, so it tells you how much of your app was looked at.
Each rule fires at most once per file. Ten eval() calls in one file produce one finding, not ten. The secret check is the exception: a file can produce one finding per secret shape it contains.
Fixing the findings
The button at the bottom reads Fix this issue for one finding, or Fix these N issues. Under it: "Hands the findings to the builder as a normal change you can review."
Pressing it:
- Re-runs the audit on the server. Findings posted from your browser are not trusted.
- Builds a prompt listing every finding, highest severity first, with its detail and its suggested fix, and instructing the builder not to change unrelated behaviour and to say plainly if a fix needs a decision from you.
- Puts a short message in your chat — "Fix the N security issue(s) found in the audit." — so the run has a readable label.
- Starts an ordinary build run.
Because it is an ordinary run, it costs credits, appears in version history, and can be restored like any other change.
The panel closes and a toast reads Fixing — watch the chat.
Two refusals you can hit:
| Message | Meaning |
|---|---|
| A build is already running. | Wait for it to finish. |
| Nothing to fix — the audit found no issues. | The re-run on the server came back clean. |
Who can run it
Anyone with access to the project, including viewers — reading files is a read. Only Fix requires write access, so a viewer sees the findings but cannot start the fix.
Next
- Downloading your app.
- Publishing an app — worth running the check before you do.