Post

We checked 100 open-source backends for routes that reach data without auth

One deterministic check over 100 Express, NestJS, FastAPI, Next.js and SAP CAP repos: 21 had a route reaching data without auth. Here is what that meant.

Try Wyro

We ran the same check that powers every public scan page over 100 open-source backends, then reviewed every unauthenticated write it flagged. The check reads a repository's routes and tables and asks one question above all: can a request reach a table without passing authentication first? No model is involved. The same code gives the same answer every time, and every repository has its own page, so each number here can be checked against the code it came from.

The short version: 21 of the 100 had at least one route reaching data without auth. Among the writes they flagged, most turned out to be public on purpose or our own blind spots, and a few looked like real gaps. The checker also had bugs: five had affected these numbers, and all five were fixed before any number went into this post.

How the 100 were picked

Popular repositories per framework, from GitHub search sorted by stars (Express with Prisma, NestJS with Prisma or TypeORM, FastAPI with SQLAlchemy, Next.js with Prisma or Drizzle), plus SAP's own CAP samples. A repository stayed in only if the check could read it as a backend: at least three routes and one table. Repositories whose maintainers opted out are excluded.

  • NestJS 34, FastAPI 22, SAP CAP 19, Express 14, Next.js 11.
  • 3,542 routes and 1,009 tables in total. The median repository has 17 routes.
  • 6 repositories could not be fetched as a single archive and were read from their first 120 source files. Their pages say so.
  • Checked on 28 September 2026. The pages recompute on every visit, so they will drift as the code and the checker change.

What came back

  • 2,226 findings: 145 errors and 2,081 warnings. 93% are warnings.
  • 21 repositories had at least one error. Every error in this study is the same kind: a route that reaches data without authentication.
  • 5 repositories had no findings at all.
  • The median repository had 11.5 findings.
Horizontal bar chart of 2,226 findings by rule. Warnings: no validator or rate limiter visible 1,165; table that no route reaches 473; readable without auth, declared on purpose 177; any signed-in user can reach it 104; data access could not be followed 88; sign-in route, public by necessity 74. Errors: route reaches a table without auth 140; nothing in the backend authenticates 5.
Errors are orange. The largest bars are warnings the check reports with low confidence, because validation and rate limiting often live where a parser cannot see them.

The errors: routes that reach data without auth

The 145 errors break down into 140 route-to-table paths with nothing in front of them, and 5 backends where nothing authenticates at all. The check reports those as one finding per backend instead of one per route; together they cover 26 routes.

  • 96 of the 140 are reads and 44 are writes.
  • 32 reach a table the check treats as sensitive: personal or secret columns, or a name like payments or tokens.
  • 60 are reported with high confidence. 80 are medium, which means auth could be applied somewhere the check can see exists but cannot read, such as a Next.js middleware.ts.
Horizontal bar chart of repositories with at least one error, by framework: Express 6 of 14 (43%), NestJS 11 of 34 (32%), Next.js 2 of 11 (18%), FastAPI 2 of 22 (9%), SAP CAP 0 of 19 (0%).
Share of repositories with at least one route reaching data without auth. SAP CAP authenticates every service by default, and none of its samples had an unauthenticated route.

What the unauthenticated writes turned out to be

A write with no auth in front of it is the finding that matters most, so we went through every one. Twelve repositories had at least one at the route level (the five backends with no auth anywhere are counted above). They split four ways:

  • Public by design (4). Anonymous analytics counters on two content sites, and CRUD demos in two starter templates.
  • Sign-up and verification routes we don't recognise yet (5). POST /users that is really a registration, or a route like send-verification-email. A sign-in route has to be public, and our exemption for them still misses these spellings.
  • Auth in middleware we don't read (1). A Next.js app that protects every /api route in Clerk's middleware. We flag it with medium confidence and say that middleware exists; we don't yet read its matcher.
  • No guard we could find (2). Write routes, including one that updates a record by id, with no guard on the route, the controller or the app.

So the honest summary is not "21% of backends are insecure". It is that a route-to-table map finds real gaps quickly, alongside things that are public on purpose, and that telling the two apart still needs a person. The check's job is to put every one of them in front of that person with the file and line.

The warnings

  • No validator or rate limiter visible (1,165, in 75 repos). Low confidence by design: request validation often happens inside DTOs or schemas, and rate limits at the platform. FastAPI routes with a typed request body already count as validated.
  • Tables no route reaches (473, in 64 repos). Usually tables used by background jobs or through code the check could not follow, not dead weight.
  • Any signed-in user can reach it (104, in 12 repos, all SAP CAP). The service authenticates but declares no role, so every user who can sign in has access.
  • Data access could not be followed (88, in 19 repos). Routes whose data access is unknown rather than absent. We report ambiguity as a finding instead of passing it silently.

What this study found wrong with the checker

A check that publishes pages about other people's code has to be right about them. Before writing this we compared the numbers against a snapshot from four days earlier, then went through the findings. Five problems had affected them. The first was fixed the day before; this study found the other four. All five are fixed and covered by regression tests:

  • FixedSAP CAP: 13 of 19 samples showed errors because unannotated services were read as open. CAP authenticates every service by default. It is now 0 of 19, and what remains is the "any signed-in user" warning.
  • FixedExpress middleware passed as an array, router.get("/", [checkJwt, checkRole(["ADMIN"])], handler), was not read, so admin-only routes were reported as open.
  • FixedIdentity tables named in camelCase, like EmailVerificationToken, were not recognised, so sign-up routes writing their own tokens were reported as unauthenticated writes.
  • FixedPOST /sessions, the REST spelling of logging in, was not recognised as a sign-in route. Only the singular was.
  • FixedMulti-word identity tables such as password_resets could never match, because names are normalised to spaces before they are compared.

One change moved numbers the other way. NestJS went from 2 repositories with errors in the snapshot to 11, because the check now follows injected services into their repositories. Routes it used to see as touching nothing are now connected to their tables, and some of them have no guard. That is the direction we want: a route the check cannot connect to data can never be reported, and it looks exactly like a pass.

What a finding does and doesn't mean

  • A finding is a structural flag, not a proven vulnerability. It says a path exists from a route to a table with no authentication on it.
  • The check does not read Supabase row-level security, does not verify that a query is scoped to the signed-in user, and does not read Next.js Server Actions.
  • Templates and samples often leave auth out on purpose. One template's maintainer told us exactly that: the right auth depends on the project. A repository can say so with "policy": { "auth": "external" } in a wyro.json, and its page lists every open route as a declaration instead of an error.

Check your own

Paste any public GitHub repository into the scan page and you get the same report these 100 have, with the file and line for every finding. To run it on every pull request, see the CI docs. The earlier, smaller version of this exercise is the 29-repository bench, which is where the parser bugs it found are written up.

  • study
  • security
  • authentication
  • open-source
  • scan