/

Engineering

/

What we found scanning 2,000 React apps for exposed keys

Engineering

What we found scanning 2,000 React apps for exposed keys

Oren Bassett

Oren Bassett

·

·

4 min read

Author

Oren Bassett

Oren Bassett

Co-founder and CTO

Published

Category

Engineering

Read time

4 minutes

All posts

Refit brings an existing React app into Chamfer. It scans the repository, moves secrets out of the source and wraps the app in sign-in, roles and logging. Before we opened it to Next.js projects on July 21, we ran the scanner over 2,000 internal repos that customers had shared with us for testing.

The results were less dramatic than you might expect, and more useful.

What we scanned

The repos came from 310 companies. Most were small: the median app had 41 components and 6 data calls. About a third were written with an AI coding assistant in the last year. The rest were older admin panels kept alive by one or two engineers.

Customers opted in through a support ticket. Each repo was scanned in an isolated environment and deleted after 14 days; we kept only the counts.

We looked for four things: credentials in the source, routes that return data without an auth check, writes to production tables straight from the browser, and dependencies with published CVEs.

The numbers

  • 23% had at least one live-looking API key in the source or in a committed .env file.

  • 31% had at least one route that returned records without checking who asked.

  • 12% wrote to a production table directly from the browser.

  • 64% carried a dependency with a published CVE, most of them rated low.

As far as the providers’ logs could show, none of the keys we confirmed had been used from outside the company. Every affected customer heard from us the same day, with the file and line.

Repos written with AI help and repos written by hand scored almost the same. Age made the difference: repos untouched for more than a year were twice as likely to hold an exposed key.

Most of the keys we found weren’t careless. They were left behind by an app that outlived the person who built it.

How the scanner tells a key from noise

Pattern matching alone throws too many false alarms. The scanner weighs three signals: the token’s shape (prefixes like sk_live_), its entropy, and where it’s used. A high-entropy string passed to a payment client gets flagged. The same string in a test fixture is noted and left alone. On the sample, weighing all three cut false positives from 1 in 3 to about 1 in 25, without missing any of the keys we had planted for testing.

// Before: the key ships to the browser
const stripe = new Stripe("sk_live_…");

// After: the call runs through a server-side resource
const refunds = await chamfer.query("stripe_refunds", { status: "open" });
// Before: the key ships to the browser
const stripe = new Stripe("sk_live_…");

// After: the call runs through a server-side resource
const refunds = await chamfer.query("stripe_refunds", { status: "open" });
// Before: the key ships to the browser
const stripe = new Stripe("sk_live_…");

// After: the call runs through a server-side resource
const refunds = await chamfer.query("stripe_refunds", { status: "open" });

What happens to a flagged key

Nothing is deleted automatically. The value moves into your workspace’s secret store, the call is rewritten to use a server-side resource, and the change appears in the scan report for a person to approve. Rotating the key is still on you, so the report links to each provider’s rotation page.

What we changed before launch

Three fixes came out of the sample. Scans now finish in under two minutes for 95% of repos, down from eight, because vendored folders are skipped. Route checks understand Next.js middleware, which removed most false alarms on App Router projects. And the report groups findings by fix, so a reviewer sees “move 4 keys” instead of four separate warnings.

The report now opens with a short summary written for the reviewer rather than the engineer: what the app reads, what it writes, and what will change when it moves in.

If you maintain an older panel

You don’t need Chamfer to act on any of this. Search your repos for committed .env files, add a secret scanner to CI, and check that every route returning records has an auth check. Our secret-detection rules are published in the docs under an open license, so you can run the same checks in your own pipeline. If you’d rather hand the job over, Refit for Next.js is on Team plans and up.

Put your first app in review this week

Create a free website with Framer, the website builder loved by startups, designers and agencies.