+Three of the new rules now catch more: JWTs decoded by hand (`JSON.parse(atob(token.split(".")[1]))`) are treated like any other unverified token, Django projects that allow every origin with credentials are flagged like FastAPI and Flask ones, and AI endpoints running as Cloudflare Workers are checked for a missing caller check.
1 change
+Nine new rules for the places AI-built apps leak most often: Supabase migrations that switch off row-level security or open writes to everyone, Firebase rules left open, AI and secret keys shipped to the browser, Base44 functions using the service role without checking the caller, roles read from user-editable metadata, unauthenticated AI endpoints, sessions trusted without verification, Python CORS wildcards with credentials, and prefix-only path checks. TanStack Start server functions are now checked for missing auth on the free plan.
6 changes
+Scanning a public GitHub repository by URL from the Scan page works again. The browser was
blocking the requests to GitHub, so every GitHub URL scan stopped with "Failed to fetch".
+Install commands now end in `@latest` (`npx xploitscan@latest scan .`, `npx -y xploitscan-mcp@latest`). Without it, npx can keep running whichever version it cached the first time, so fixes never reach you. If your MCP config or scripts use plain `xploitscan-mcp` or `xploitscan`, add `@latest` to get current rules and fixes.
+A Pro or Team free trial includes everything on the plan you are trying, including all 214
rules in the CLI, GitHub Action, VS Code extension and MCP server. Free trials are limited to
one per person.
+SQL injection detection now works in Express files that use route parameters. A route like
`/users/:id` used to switch the SQL injection check off for the whole file, so a query
built from `req.params` went unflagged. Update to CLI 1.12.4 or later; the web scanner and
GitHub checks pick it up automatically.
The homepage scanner now judges pasted code on its content. It used to label every paste as a
demo file, which could lead the AI false-positive filter to dismiss real findings as example code.
+Fewer false alarms on static sites and browser games. Namespaced storage keys like `myapp.settings` are no longer reported as secrets. Rate-limit findings need a real server, and N+1 findings need a database call inside the loop. SRI isn't suggested for vendor scripts that update in place, like Stripe.js. The CSP fix is built from your page's own scripts, and the nosniff and clickjacking fixes name the HTTP header. `// VC###-OK` markers now work on the website, as they already did in the CLI.
+The VS Code extension and the MCP server now work like the CLI: 30 free rules with no
account, all 214 on a paid plan. Generate an API key under Settings → API Keys on an Indie,
Pro or Team plan, then run "XploitScan: Enter API Key" in VS Code, or add
`XPLOITSCAN_API_KEY` to the MCP server's `env` block. Scans still run locally; your code is
never uploaded. This arrives in VS Code extension 1.3.0 and MCP server 1.6.0;
earlier versions still run every rule without a key.
1 change
+`npx xploitscan cursor install` works again. It writes XploitScan's security rules to
`.cursor/rules/xploitscan-security.mdc` and `.cursorrules` so Cursor follows them while it
writes code. Earlier versions of the CLI rejected the command. Update to CLI 1.12.2 or later.
5 changes
Safer team invitations and clearer trial reminders
+The GitHub Action and GitLab and Bitbucket pipelines now use
`XPLOITSCAN_API_KEY` to run your plan's full rule set and add the run to your
dashboard, once per CI job.
If the key is mistyped or revoked, the log now says so instead of quietly
scanning with the free rules. If your plan's scan limit is reached, the build
keeps going on the free rules rather than failing.
+We updated our Privacy Policy and Terms of Service. They
now describe how long we keep your data, what deleting your account removes, and
how Free Trust Page data is handled. If you had an account before this update,
you'll see a short notice about it in the app for the next 30 days, or until you
dismiss it.
+Joining a team is now always your choice. When someone invites you, the
invitation waits in Settings → Team, with Accept and Decline buttons and a note
explaining what the team will be able to see if you accept. Invitations only
match email addresses you've verified.
Team owners will see invitees as Pending until they accept. If you invited
someone before this change, use Resend on their invitation so they get the email
with the link to accept.
+The trial-ending reminder in the dashboard now counts down in hours when a trial
ends within a day and a half, the same way the reminder email does. Before, it
rounded up to whole days, so a trial ending tomorrow night said "2 days".
+Trial reminders now say exactly what happens when a trial ends: the renewal
amount, the date your card will be charged, and how to cancel beforehand if you
don't want to continue.
The Privacy Policy and Terms now describe data retention as it works today.
Scan results, audit logs and checklists are kept while your account is active,
and you can delete scans at any time, or delete your account from Settings to
remove your data.
1 change
Plan changes and team seats
+Switching plans now updates the subscription you already have, wherever you
start from — pricing, the scan screen, a feature prompt or settings. Each route
ends at the same confirmation, with prorated billing.
Team members deactivated when a plan lapsed can be brought back: they show as
**Inactive** with a Re-invite button beside them.
Removing someone from a team ends their access to shared scans and nothing
else — their own scan history stays with their account.
2 changes
Plan status that stays current, and whole-folder uploads
+Your plan, trial dates and renewal date are now reconciled against Stripe on a
schedule, instead of only when a billing event reaches us or you happen to open
the app. A subscription that changed while you were away shows its real state
the next time you look.
Backing out of Stripe checkout also returns you to the plans now, rather than
to the settings page.
+The web scanner now takes a whole folder — dragged in, or picked with Browse
Folder. It walks subdirectories and skips `node_modules`, build output and
`.git`, so a real project uploads in one step instead of a file selection.
It also tells you when a drop contained nothing it can scan, rather than
looking as though nothing happened.
17 changes
Configuration, quality checks, team access and a round of fixes
+New accounts receive the welcome sequence — day 1, day 3 and day 5 — and the two-day "anything blocking your first scan?" note. Accounts created from today onwards enter the sequence; nobody receives a backlog of older messages.
+A page that cannot load your data says so instead of showing you an empty one. This covers your security checklist, account activity log, team, team scans, trend chart and saved scan intent — and the checklist no longer saves over itself when it could not load.
+Site-verification tokens are no longer reported as possible secrets. Google, Bing, Yandex and Facebook domain-ownership tokens are published on purpose — Next.js renders them into a meta tag on every page — so flagging them cost you grade for doing the right thing. Secret detection also now weighs the name a value is assigned to, so a credential called something like `apiKey` is caught even when the value itself looks unremarkable. Your next scan may report secrets earlier ones did not.
+The compliance page now covers NIST SP 800-53, the CIS Critical Security Controls and the HIPAA Security Rule, alongside SOC 2, ISO 27001, OWASP and CWE. Each finding maps to specific controls, with the control's official title shown so a reviewer can check it. As before, this is an informational mapping to help you answer a security questionnaire — not an audit, a certification, or a claim that any control is satisfied. Long control lists collapse, so a tab opens on what matters.
+Long lists are paged. A scan report showed every finding at once — up to 500 full cards on one page — and now shows 25 at a time, worst first, so the criticals are always on the first screen. Links that jump straight to a finding still land on it, whichever page it is on. Your saved questionnaire responses are paged the same way.
+A pull request whose scan failed gets scanned on the retry. GitHub re-sends a delivery when a scan does not complete, but the record we write to avoid scanning the same pull request twice was also swallowing those retries — so a scan interrupted by a timeout or a transient error left the pull request with no check at all, looking exactly like the App not being installed. An interrupted scan now releases its claim, and a completed one is still never repeated.
+Downgrading from Team now removes team seats, whichever plan you move to. Moving to Indie left every member active, so ex-teammates kept a stale membership that locked them out of managing their own billing. Separately, "Free Months Earned" on the referral panel now counts credits that were actually applied to your account rather than conversions — a referral that converted while the credit could not be applied is shown as pending instead of counted as paid.
+The unprotected-payment-webhook check no longer reads comments as code. A file that calls the Stripe API and happens to explain itself — mentioning a webhook, or naming an event type in a sentence — could be reported as a critical unprotected webhook even when nothing external can reach it. The check now looks at code only, in both directions: a comment can no longer raise the finding, and can no longer silence it on a webhook that really is unprotected.
+Four operations now finish what they promise. Deleting your account stops rather than proceeding if the subscription cannot be cancelled first, so a closed account can no longer keep billing. The free Trust Page success screen says plainly when we could not email your Edit URL — it is the only way back in, so save it. Removing a team member permanently deletes their scans first, so a failure leaves the action retryable. And a CLI login that times out no longer permanently consumes an API key slot.
+New: a second catalogue of checks, about correctness rather than security. The scanner's 214 rules ask whether your code can be attacked. These twelve ask whether it tells the truth — a page that shows an empty list when the database was unreachable, an action that reports success it never got, a record written before the payment it stands for, a control only a mouse can reach. They are reported separately from security findings and are not counted among the 214.
+`// VC###-OK:` and `// scanner-OK:` now work on every rule. They were described
as a general mechanism and applied to only a handful, so on most rules the
comment did nothing and said nothing.
`rules.disable` now applies to every engine, not just the VC rules — entropy,
config, cross-file, dependency, semgrep and gitleaks findings too.
Also honoured: `scan.exclude`, `scan.extensions`, `scan.maxFileSize`,
`output.format`, `output.verbose` and `ai`. The GitHub Action gains a
`fail-on-quality` input.
+The Custom Rules tab in Settings is gone. It let you write rules and saved them
in your browser, but no scan ever read them, so nothing written there affected a
result. It was labelled "Not applied to scans yet"; now it is removed rather
than pending.
To turn off a built-in rule, use `rules.disable` in `.xploitscanrc`, or
`.xploitscanignore` to scope one to certain paths.
+Three checks decided things by searching the whole file, so a second component
in the same file could answer for the first. An autosave warning could be
raised from an unrelated component's state, and a stale-response warning from
an unrelated input.
All three now resolve names the way the language does, and a new test runs every
published example twice — once with a neighbouring component added — so this
cannot come back quietly.
+Every scan now runs a second set of checks beside the security rules. They look
for code that runs without error and quietly reports something that did not
happen — an empty list because the request failed, a "Saved" that never landed, a
control only a mouse can reach.
Free on every plan, in their own section, never counted toward your security
grade. Worked examples for each at /quality.
+Two checks were narrowed to stop reporting correct code, and the narrowing was
too tight. A failed read shown as an empty state went unreported when the
response was cast, destructured or assigned rather than declared; an autosave
armed by a failed load went unreported in any file with a second component
using the same flag name.
Both now match on shape rather than one spelling, each pinned by a test.
+Admins can invite teammates again. The invite form was offered to them and then
refused with an upgrade prompt for a plan their team already had.
A membership row left behind by an ended plan also no longer restricts the
person holding it — it could block them from the billing portal and from
scanning on their own account. Roles are now read from a team with a live
subscription, so they end when it does.
+Cancelling a Team plan now clears its seats. Members' rows previously stayed
active after the final invoice, and the team trend chart kept aggregating their
scans.
Both halves are fixed: cancellation deactivates non-owner seats, and the trend
endpoint checks the team owner's subscription the way every other team endpoint
already does. Teams inside Stripe's payment-retry window keep full access, and
personal trends are unaffected.
40 changes
The scanner now checks your code against what you said it would do
+Actions that fail now say so. Saving notification preferences, revoking an API key or a session, deleting a saved questionnaire response, renaming a device and creating a checklist share link all report the outcome instead of assuming success, and a failed rename puts the old name back.
+Claiming a free Trust Page after signing up completes cleanly. If the page has already been claimed by another account, you are told that directly rather than sent back to your inbox.
+Deleting your account now removes everything tied to it, including your Trust Page — which previously stayed reachable by its public link — along with saved questionnaires and their per-prospect responses, custom domain verifications, project descriptions, your AI-analysis preference, dismissed prompts, review reminders and feedback. A connected GitHub App is unlinked rather than deleted, since the App still exists on GitHub.
+Saved scans now keep up to 500 findings, most severe first, so the detail you open matches the counts you were shown. Scans saved earlier kept fewer — re-run one to see everything it found.
+New: an intent check. Every other thing a scanner tells you is about code; this one is about the gap between your code and your own description of it. If your README says "customers can only see their own orders" and an endpoint returns orders without checking who is asking, you get that as a sentence: what you wrote, where you wrote it, and the endpoint that disagrees. It reads README.md, CLAUDE.md, AGENTS.md, .cursorrules, and a .xploitscan-intent file if you keep one.
+Benchmark figures quoted on the comparison and blog pages now match the benchmark page: XploitScan catches all 16 of the held-out third-party set.
+The benchmark page now states which rule set produced our score. Ours uses the full catalogue — what the MCP server and a paid CLI run — while Semgrep and Bearer are measured with their free rules. The number is unchanged; the comparison is now described accurately.
+The intent check reads the file your AI tool already wrote: .cursorrules, .windsurfrules, .github/copilot-instructions.md, .bolt/prompt, and saved chat transcripts under .specstory/history — where it reads your turns and ignores the assistant's, because the model's summary of what it built is not a specification of what you asked for.
+If your project says nothing about what it is for, the scanner asks. One question, once, saved to a plain .xploitscan-intent file you can edit. It only asks in a real terminal — never in CI, never when output is piped, never inside a git hook. `xploitscan intent` does the same deliberately, and `--intent "..."` passes a sentence for a single run.
+The intent check ignores code that does not count toward your grade — vendored, generated and bundled example code. It also reads the exceptions a sentence states about itself: "all API routes except /api/health require authentication" no longer treats a correctly-public health check as a contradiction.
+The intent check only reports a disagreement it can prove. Each line points at a finding the scan already produced, with a file and a line. A claim the scan cannot dispute is left alone rather than dressed up as a pass. Run a scan and it appears on its own — there is no flag to find.
+Why the intent check is worth having if you build with AI tools: you described what you wanted in the prompt, and the generator produced something plausible. It currently understands six kinds of claim — who owns which rows, what needs a login, what is admin-only, what is private, which webhooks are verified, and what is rate limited.
+Paid plans were running an older rule catalogue on the command line than the one we publish. Your next CLI scan runs the current set. If it reports findings last week's did not, those findings were already in your code.
+Payment webhooks written the way most AI builders produce them — a Next.js route under app/api/webhooks/stripe/ that never names Stripe in the body — are now checked for signature verification. If that describes your app, your next scan may report a critical finding that was always there. The check also reads the arrow form of the handler, and stays quiet when verification lives in a helper.
+The MCP server no longer reports some findings twice. The findings were real; the count was wrong. Fixed in MCP server 1.3.0. The VS Code extension has the same fix, but it has not reached the Marketplace yet; the version there today (1.1.2) still counts some findings twice. The CLI and web scanner were never affected.
+The MCP server starts reliably on installs whose path runs through a symbolic link — the ordinary case for npx on macOS and for anything installed with pnpm. Previously it could launch and never answer your editor. Fixed in MCP server 1.4.1.
+Three checks stopped firing on correct code. Routes meant to be reached without logging in — Open Graph images, sitemaps, robots, manifests, status badges — are no longer reported as missing authentication. Neither are magic-link routes, where the unguessable token in the link is the credential. And a constant that merely names a cookie is no longer mistaken for code that sets one, while a Secure flag switched on by environment now counts as set.
+A saved project description is now kept per project, so a sentence about one project is never checked against another. Anything saved before today is no longer applied — the scanner will ask again when it next needs one.
+The dashboard's promotional cards can be dismissed, and stop calling themselves new after you have been using the product for a while.
+"Last scanned" times are accurate on the dashboard, the reports tab and the scan table. Recently-completed scans could show a negative age.
+The Launch Checklist has a way in. It reads your latest scan, tracks what is left before you ship, lets you mark items done or not applicable, add your own, export a PDF and share a link — and it feeds the completion percentage on your Trust Page. It is now in the Scan menu, on your dashboard, and on both scan result views.
+Rule counts now read the same on the Docker image and the CLI README: 214 rules across 14 categories.
+Indie plans get on the command line what the pricing page says they get. Indie accounts can now create an API key and download the full rule catalogue, so a CLI scan runs all 214 rules rather than the free 30. Web scans always ran the full set. If you are on Indie, generate a key in Settings → API Keys and your next CLI scan picks it up.
+If your card fails, you keep everything you pay for while Stripe retries — scan limits, Trust Page editing, pull-request scanning, API keys, and the same for every member of a team. Access now follows one rule for the whole retry window, matching what the billing banner has always said. Nothing changes when a subscription is genuinely over.
+All Scans on the Reports tab is paged — 25 at a time, with "Showing 26–30 of 137 scans" so you can tell where you are. If you are at the 500-scan ceiling it says so, rather than leaving you to assume that is everything.
+The compliance page measures coverage against the actual standards rather than against our own list of mapped controls: 11 of SOC 2's 33 common criteria, 13 of ISO 27001's 93 Annex A controls, and the OWASP Top 10 in full. Each framework also says which parts a code scanner cannot speak to — SOC 2's control-environment and risk-assessment criteria are assessed by an auditor reading policies, not by reading source. The mapping itself is unchanged.
+Export My Data now includes your Trust Page configuration, the security questionnaires you filled in and every per-prospect copy of them, your custom domain verifications, what you wrote about your projects, and your AI-analysis preference. Keys and claim tokens are still deliberately excluded.
+Checklist completion times are accurate on both your checklist and the shared version you send to customers. Items the scanner passed for you showed "Completed just now" indefinitely.
+Text across the signed-in pages is readable. Labels, descriptions and helper text on the dashboard, reports, settings, checklist and scan pages were rendering in greys too faint against the dark background to meet the accessibility standard. Anything you are meant to read is now a legible tone.
+The AI analysis opt-out is enforced on every path. Turning it off in Settings already stopped scans from the web app sending code excerpts for false-positive filtering; GitHub App pull-request scans now consult the same setting, and a preference we cannot read is treated as "off" rather than "on". If the filter is skipped for that reason, the pull request check says so.
+The dashboard trend chart and the weekly digest email cover the full window you asked for. The oldest day of each window was being left out.
+A page that cannot reach your scan history now says so, instead of showing the screen a brand-new account sees. A scan that fails to save reports that too, rather than looking identical to one that saved.
+The scan limit notice matches the plan you are on — which cap you reached and when it actually resets — and no longer offers you an upgrade to the plan you already have. Scanning a public GitHub repository also reports a failed save the same way every other scan does.
+The subscription confirmation page reports what actually happened to your subscription. The 7-day trial applies to your first Pro or Team subscription; if you upgraded or re-subscribed you are charged straight away, and the page now says so instead of announcing a trial.
+The plan comparison in Settings matches the public pricing page. Scan history is available to free accounts and is no longer shown as something an upgrade unlocks, and both tables quote the same rule count.
+Team scan history is readable. Teammates on an active Team plan can open each other's scans, which is what shared scan history was meant to be. Deleting stays with the person who ran the scan.
+Team seats are counted. The invite form shows how many of your five included seats are in use, and an invitation past the fifth is declined with a note about adding more. Existing teams keep every seat they already have.
+The CLI tells you when a scan did not reach your dashboard. It prints one line — "Scan saved locally only" — rather than printing the report exactly as usual and leaving the scan quietly missing. Your results and exit code are unaffected.
+Dialogs and table sorting work without a mouse. The pop-up panels on the dashboard are announced as dialogs, move keyboard focus into themselves and close on Escape, and the scan history table can be sorted from the keyboard with the current sort announced.
+Form fields on the signed-in pages are labelled properly. The webhook URLs and custom-rules editor in Settings, every field in the Trust Page panel and the questionnaire editor, and the three scan pickers on Reports all had their label text sitting beside the field rather than attached to it — so a screen reader announced an unlabelled box, and clicking the label text did not put the cursor in the field. The scan pickers also now say that they open a list, and whether it is open.
13 changes
Findings now tell you the URL they sit behind — CLI 1.5.0
+CLI 1.5.0 — machine-readable scan output now names the AI builder that scaffolded the project. The terminal has shown this for a while ("Detected: Lovable project"); JSON, SARIF and the Splunk, Elastic and Datadog formats did not carry it at all, which is exactly where it is most useful — grouping or filtering findings by builder across many repositories in CI. It is context only and never affects a finding's severity or your grade.
+SARIF output was reporting the scanner's version as 0.1.0 regardless of which version produced it. If you upload results to GitHub code scanning, runs from different releases were indistinguishable. It now reports the real version.
+A finding used to give you a file and a line. It now also tells you the URL that reaches it, where one exists: "reachable at /api/webhooks/clerk". The scanner reads your project's own route table rather than guessing from folder names, so the path it shows is the path your app actually serves. Next.js, TanStack Router, React Router, Express and Flask are supported. It appears in the terminal, in JSON output, and on the finding card in the web report. Route information is shown for context only and does not change any finding's severity or your grade.
+Two new rules for account state that lives in the browser. The first flags an entitlement or role — a plan, a tier, an admin flag — read out of localStorage or sessionStorage and then used to decide what the user can do. Anyone can edit those values in their own browser, so the check has to happen on the server. The second is informational and flags per-account lifecycle state, such as whether onboarding is finished, being decided by browser storage alone: it is not a vulnerability, but it is why a signed-in user gets greeted as a first-time visitor on a new device.
+That second rule came from our own bug, and it is fixed here too: the "Welcome to XploitScan" prompt no longer appears for people who have already run a scan. Whether you are new is now answered by your scan history rather than by a flag in the browser, so signing in from a different browser or clearing site data no longer makes the product forget you.
+A scanner coverage fix worth naming plainly: files inside a folder called "onboarding" were being skipped during scans. The exclusion was meant to ignore our own demo fixtures and was matching far too broadly. If your project keeps real code in an onboarding folder, it was not being scanned, and your next scan may report findings that were always there. We would rather tell you that than let the number quietly change.
+Platform detection no longer mistakes writing about a tool for using one. A page or document that merely mentions Lovable, Bolt or Replit was being treated as evidence the project was built with it.
+The authentication check now recognises four kinds of endpoint it previously could not see at all. Vercel serverless functions, Netlify functions, Supabase Edge Functions and Next.js server actions were simply invisible to it — not judged and found safe, but never examined. Server actions are the notable one: a file marked "use server" turns every exported function into a callable endpoint with no route file and no URL anywhere in your project, so tools that work from routes cannot see them at all.
+For Supabase projects the scanner now reads supabase/config.toml. That file decides whether Supabase checks a caller's token before your edge function runs, and it is the difference between an endpoint anyone can call and one only signed-in users reach — a fact that simply is not visible in the function's own code. It cuts both ways: functions the platform already protects are no longer reported, and functions with that check turned off are now reported with confidence.
+The IDOR check asks a better question. It used to scan a whole file for any sign of an ownership check and stay quiet if it found one anywhere; now it looks at the database query itself. A file that displays the current user's name in one place and fetches records by a raw id in another is no longer treated as safe because of the first one.
+The benchmark page now separates blind recall from total. One case in the held-out set — a broken-access-control bug from DVNA — was read in detail before the rule that catches it was written, so it can no longer measure how well the scanner handles code nobody has looked at. It is excluded from the headline figure and named on the page rather than quietly counted as a win.
+CLI 1.5.0 and rule set 1.22.0 are on npm. If you run the scanner with npx you already have them; if you installed it globally, run `npm i -g xploitscan@latest`.
+The exported questionnaire PDF had an invisible title. Print or Save as PDF on the security questionnaire produced a document whose heading did not appear — the title was printing white on white, so the file you hand to a procurement team opened with a blank space where "Security Questionnaire" should be. Fixed, and the site navigation and footer no longer print with it: they are part of the app, not part of the document you are sending.