---
title: "The vulnerabilities popweb is tested against."
description: "@popcomputer/web is tested against 70 published vulnerabilities from Next.js, Laravel, Hono, Rails, Django and more: each one, and the test."
url: "https://web.popcomputer.com/security/tested-against"
updated: "2026-10-30"
---

# The vulnerabilities popweb is tested against.

@popcomputer/web’s security suite attacks the framework the way published vulnerabilities attacked others: 70 advisories from 25 frameworks and libraries, among them Next.js, Laravel, Hono, Better Auth, Rails, Django and SvelteKit. Each test names the advisories it guards against, and the package’s publish step runs every test, stopping if one fails. A passing test shows popweb isn’t affected by that bug as it was written, not that the framework can’t have bugs of its own.

## How the suite works

Each file in the framework’s [tests/security](https://github.com/PatrickOgilvie/popcomputer-web/tree/main/tests/security) folder attacks one class of bug the way it attacked another framework, and its opening comment names the advisories it guards against. Most run against a small app that says, for every record, who may load it, and logs every statement its database receives, so a test can check both what a request got back and what it changed. A test there that fails is a vulnerability, not a style regression.

Below, each class has a short account of how the bug works and how popweb settles it, then a line for each advisory: the project it hit, and what went wrong there. A passing test shows popweb isn’t affected by the bug as the test writes it, and where a test covers only part of an advisory, its section says so. It doesn’t show the framework has no bugs nobody has found yet, and it doesn’t test your app’s own rules.

## Where the advisories come from

The 70 advisories come from 25 projects. Most are web frameworks; some are libraries frameworks are built from, such as qs, Zod and Drizzle. Two of the tests also come from this framework’s own past: 1.3.0 served an upload typed image/png;x=,text/html back as a page, and stored publicly a response that an API key in a header of its own had signed in. Both were fixed in 1.4.0.

- Hono: 13 advisories
- Better Auth: 7 advisories
- Laravel: 6 advisories
- Next.js: 6 advisories
- Django: 4 advisories
- Rails: 4 advisories
- SvelteKit: 4 advisories
- Nuxt: 3 advisories
- React Router: 3 advisories
- Fastify: 2 advisories
- Koa: 2 advisories
- qs: 2 advisories
- Starlette: 2 advisories
- Drizzle: 1 advisory
- Effect: 1 advisory
- Express: 1 advisory
- FastAPI: 1 advisory
- Gin: 1 advisory
- Livewire: 1 advisory
- NextAuth.js: 1 advisory
- path-to-regexp: 1 advisory
- Phoenix: 1 advisory
- TypeORM: 1 advisory
- Valibot: 1 advisory
- Zod: 1 advisory

## Another spelling of a protected path

A guard that matches a path as text, and a router that decodes it, can disagree. An encoded slash, a doubled one or a capital letter then reaches the page while the guard looks the other way.

popweb doesn’t decide who may see a record by the path’s text. The route that names a record says who may load it, and the lookup’s own query enforces that, so whichever spelling the router accepts, another member or a visitor gets nothing. The test tries fifteen spellings of one record’s path, among them /PROJECTS/p1, //projects/p1, /projects%2Fp1 and /projects/x/../p1, and seven of a guarded organisation’s.

- [CVE-2026-29045](https://www.cve.org/CVERecord?id=CVE-2026-29045) Hono: the router and serveStatic decoded %2F differently, so a protected file could be read past its middleware.
- [CVE-2026-39407](https://www.cve.org/CVERecord?id=CVE-2026-39407) Hono: repeated slashes in a path got a request to serveStatic past its middleware.
- [CVE-2026-53721](https://www.cve.org/CVERecord?id=CVE-2026-53721) Nuxt: route rules matched paths case-sensitively and the router didn’t, so another case got past their middleware.
- Tested in [tests/security/requests.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/requests.test.ts).

## Headers that tell a framework about the request

Some frameworks act on headers a client can write: one that says the request came from middleware, one that asks for different page data, one that names the host. Whoever sends the header decides what the framework believes.

The test sends thirteen such headers, among them x-middleware-subrequest, Inertia’s own headers, method overrides, X-Original-URL and X-Forwarded-Host. None lets a visitor or another member load a record they couldn’t, none makes a shared cache keep a response, a method override changes nothing another member may not change, and no redirect names a host a header gave.

- [CVE-2025-29927](https://www.cve.org/CVERecord?id=CVE-2025-29927) Next.js: an x-middleware-subrequest header made it skip middleware, authorisation checks included.
- [CVE-2025-43865](https://www.cve.org/CVERecord?id=CVE-2025-43865) React Router: a header sent by the client replaced a page’s loader data.
- [CVE-2026-27959](https://www.cve.org/CVERecord?id=CVE-2026-27959) Koa: the hostname it reported came from Host and X-Forwarded-Host headers a client can set.
- [CVE-2026-3635](https://www.cve.org/CVERecord?id=CVE-2026-3635) Fastify: the host and protocol it reported could be set with X-Forwarded-Host and X-Forwarded-Proto from any connection.
- Tested in [tests/security/requests.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/requests.test.ts).

## Writes from another site

A page on another site can post a form to yours, and the visitor’s browser sends your cookies with it. Frameworks check where such a write came from, and the checks have been fooled by a body type they didn’t expect, a type in capitals, Origin: null, and methods they forgot.

popweb refuses a write from another site before its route is looked up. The test sends nine body types and spellings, no type at all among them, from four other origins, null included, and every method that changes something. Each is refused with a 403 and writes nothing, and so is a write whose only Referer names another site.

- [CVE-2024-48913](https://www.cve.org/CVERecord?id=CVE-2024-48913) Hono: a write with no Content-Type passed its CSRF check.
- [CVE-2024-43787](https://www.cve.org/CVERecord?id=CVE-2024-43787) Hono: a Content-Type in capitals passed its CSRF check, which compared it case-sensitively.
- [CVE-2021-32677](https://www.cve.org/CVERecord?id=CVE-2021-32677) FastAPI: it read JSON from a body sent as text/plain, which a form on another site can send.
- [CVE-2023-29003](https://www.cve.org/CVERecord?id=CVE-2023-29003) SvelteKit: a write with a Content-Type its CSRF check didn’t list, such as text/plain, got past it.
- [CVE-2023-29008](https://www.cve.org/CVERecord?id=CVE-2023-29008) SvelteKit: the same check was then passed by a type in capitals, such as text/plaiN.
- [CVE-2026-27978](https://www.cve.org/CVERecord?id=CVE-2026-27978) Next.js: Origin: null got past the CSRF check on Server Actions, read as no Origin at all.
- [CVE-2026-53663](https://www.cve.org/CVERecord?id=CVE-2026-53663) React Router: PUT, PATCH and DELETE requests weren’t checked for forgery.
- Tested in [tests/security/cross-site.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/cross-site.test.ts).

## Fields a form shouldn’t accept

Mass assignment is a form setting a field it never offered, such as who owns a record. Its cousins reach further: a key that names the object prototype changes every object the server holds.

In popweb each form is a schema, and only the fields it declares reach your code. The test posts owner, id and visibility fields in every spelling the advisories used, prototype keys in JSON, forms, multipart bodies and the query string, keys named after the methods every object inherits, and body values that try to swap the record the path names. None changes a row it shouldn’t, and none reaches the prototype. An array or an object where the form takes text is refused.

- [CVE-2020-24941](https://www.cve.org/CVERecord?id=CVE-2020-24941) Laravel: a key written foo->bar set a guarded JSON column.
- [GHSA-44pg-c29v-hp6r](https://github.com/advisories/GHSA-44pg-c29v-hp6r) Laravel: a key written users.name was stripped to name, getting past the model’s guarded fields.
- [CVE-2020-8164](https://www.cve.org/CVERecord?id=CVE-2020-8164) Rails: permitted parameters were returned unfiltered, getting past Strong Parameters.
- [CVE-2025-60542](https://www.cve.org/CVERecord?id=CVE-2025-60542) TypeORM: a field sent as city[role]=admin set another column, through SQL injection in save and update.
- [CVE-2022-24999](https://www.cve.org/CVERecord?id=CVE-2022-24999) qs: a __proto__ key in parsed input polluted the object prototype.
- [CVE-2026-56763](https://www.cve.org/CVERecord?id=CVE-2026-56763) Hono: a __proto__ key in a body parsed with dot notation polluted the object prototype.
- [CVE-2017-1000048](https://www.cve.org/CVERecord?id=CVE-2017-1000048) qs: a bracketed key got past its guard on the names Object.prototype holds.
- [CVE-2026-84504](https://www.cve.org/CVERecord?id=CVE-2026-84504) Fastify: a body with a value property had that property replace the validated body.
- [CVE-2026-44574](https://www.cve.org/CVERecord?id=CVE-2026-44574) Next.js: a query parameter changed the route parameter a page received, past its middleware.
- Tested in [tests/security/input.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/input.test.ts).

## Uploads served back as pages

A file a visitor uploads is served from your app’s own address. If a browser reads it as a page, its script runs as your app. The checks have been fooled by a type the client chose, an SVG sent as an image, and a type that’s really a list, which a browser reads by its last entry.

In popweb an upload must be one media type, and markup sent as an image is refused, whatever its first bytes claim. A served file whose type isn’t one media type is sent as plain bytes. Anything but an image, a PDF, plain text, JSON, audio or video is shown only inside a sandbox, so its script can’t run as your app, and a download’s name is written as one quoted value, however it tries to break out of its header.

- [CVE-2024-47823](https://www.cve.org/CVERecord?id=CVE-2024-47823) Livewire: an upload’s extension was guessed from the MIME type the client sent.
- [CVE-2018-16477](https://www.cve.org/CVERecord?id=CVE-2018-16477) Rails: the type and disposition Active Storage served a file with could be changed, so an HTML file ran inline.
- [CVE-2024-26144](https://www.cve.org/CVERecord?id=CVE-2024-26144) Rails: a flaw in how Active Storage served uploaded files.
- [CVE-2021-39178](https://www.cve.org/CVERecord?id=CVE-2021-39178) Next.js: an SVG served through its image optimisation ran script.
- [CVE-2022-36359](https://www.cve.org/CVERecord?id=CVE-2022-36359) Django: a file name escaped its Content-Disposition header, for a reflected file download.
- [CVE-2023-29401](https://www.cve.org/CVERecord?id=CVE-2023-29401) Gin: FileAttachment didn’t sanitise a file name, so it could escape its Content-Disposition header.
- Tested in [tests/security/served-content.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/served-content.test.ts).

## Open redirects

Apps send people on after they sign in, or back to a form that failed, and the address often comes from the request. If it can name another site, a link on your own domain sends people to a lookalike. Checks have been fooled by //evil.com, which a browser reads as another host, by encoded and backslashed spellings, and by javascript: addresses, which run script instead of leaving.

In popweb the page to return to after signing in, and the page a failed form goes back to, are only ever the app’s own: the test tries fifteen spellings of another site. A redirect to a javascript: or data: address is refused, since an Inertia visit would run it. A path filled from a form’s fields leaves a value that isn’t one path segment, such as an empty one or .., unfilled, so the redirect stays on its route and site.

- [CVE-2025-27143](https://www.cve.org/CVERecord?id=CVE-2025-27143) Better Auth: a scheme-less address such as //evil.com passed its trusted-origin check.
- [GHSA-vp58-j275-797x](https://github.com/advisories/GHSA-vp58-j275-797x) Better Auth: a crafted address got past its trusted origins, wildcard patterns included.
- [CVE-2025-53535](https://www.cve.org/CVERecord?id=CVE-2025-53535) Better Auth: a check that an address started with a trusted origin let another site through.
- [CVE-2025-62595](https://www.cve.org/CVERecord?id=CVE-2025-62595) Koa: back() read //evil.com as a path, and redirected there.
- [CVE-2024-29041](https://www.cve.org/CVERecord?id=CVE-2024-29041) Express: an encoded Location got past allowlists.
- [CVE-2017-1000163](https://www.cve.org/CVERecord?id=CVE-2017-1000163) Phoenix: a redirect to a slash, a line break and a host left the site.
- [CVE-2024-34343](https://www.cve.org/CVERecord?id=CVE-2024-34343) Nuxt: navigateTo followed a javascript: address, running its script.
- [CVE-2026-22029](https://www.cve.org/CVERecord?id=CVE-2026-22029) React Router: a redirect to a javascript: address ran its script.
- Tested in [tests/security/redirects.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/redirects.test.ts).

## Signed cookies moved or changed

A signed cookie is one the server can trust, because changing it breaks its signature. That only holds if a value signed for one cookie can’t pass for another.

In popweb a value signed under one name is no value under another, and one that’s changed, or stripped of its signature, reads as nothing. Each cookie is read through the schema it was declared with.

- [GHSA-2ffv-r4r9-r8xr](https://github.com/advisories/GHSA-2ffv-r4r9-r8xr) Laravel: a cookie’s value was valid under another cookie’s name.
- [CVE-2026-6873](https://www.cve.org/CVERecord?id=CVE-2026-6873) Django: the salts its signed cookies were signed with could collide.
- [CVE-2018-15133](https://www.cve.org/CVERecord?id=CVE-2018-15133) Laravel: a cookie was decoded into objects, which could run code.
- Tested in [tests/security/cookies-and-pages.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/cookies-and-pages.test.ts).

## Page data breaking out of the HTML

Inertia, which popweb’s pages run on, writes each page’s data into the HTML as JSON inside a script tag. A value containing </script> can end that script early and start one of its own, and an HTML comment opener can swallow the rest of the page, which [Inertia](https://github.com/inertiajs/inertia/pull/3253) and [inertia-laravel](https://github.com/inertiajs/inertia-laravel/pull/911) each fixed in a pull request.

The test writes seven such values into page data, as values and as keys, and checks that each reads back exactly and that the page gains no element.

- [CVE-2025-32388](https://www.cve.org/CVERecord?id=CVE-2025-32388) SvelteKit: search parameter names went into the boot script unescaped, so </script> could break out of it.
- Tested in [tests/security/cookies-and-pages.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/cookies-and-pages.test.ts).

## A shared cache keeping one visitor’s page

A cache in front of your app keeps a response and hands it to the next visitor who asks for the same address. If that response was made for someone signed in, or depended on a cookie or a header, the next visitor sees another person’s page.

In popweb a route given a cache keeps nothing that a signed-in, authorised, partial or failed request produced, nor anything whose making read a header, a cookie or the request’s state, and what it keeps varies on cookies and on Inertia’s page variant. A route whose record depends on the visitor can’t be given a cache at all: registering one fails.

- [CVE-2026-71316](https://www.cve.org/CVERecord?id=CVE-2026-71316) Nuxt: page payloads were cached by path alone and served before middleware ran, so one user’s data reached others.
- [CVE-2025-49005](https://www.cve.org/CVERecord?id=CVE-2025-49005) Next.js: a page’s variants were cached under one key, with the Vary header left out.
- [CVE-2025-57752](https://www.cve.org/CVERecord?id=CVE-2025-57752) Next.js: its image optimisation cached different responses under one key.
- [CVE-2026-48588](https://www.cve.org/CVERecord?id=CVE-2026-48588) Django: its cache kept a response that varied on cookies when the request carried unrelated ones.
- [CVE-2026-35193](https://www.cve.org/CVERecord?id=CVE-2026-35193) Django: its cache kept a private response because Vary left out Authorization.
- [CVE-2026-44457](https://www.cve.org/CVERecord?id=CVE-2026-44457) Hono: its cache middleware ignored Vary: Cookie and Vary: Authorization, so users saw each other’s responses.
- Tested in [tests/security/cache-and-isolation.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/cache-and-isolation.test.ts).

## Requests at once seeing each other’s user

One Worker serves many requests at the same time. If something about a request, such as its route parameters or who’s signed in, is kept where another request can read it, a request that pauses for the database can carry on as someone else.

The test sends forty requests at once from two members, each pausing between reading its user and loading its record, and each sees only its own. A lint rule also reports module-level state in your own code, which every request a Worker serves shares.

- [CVE-2023-50710](https://www.cve.org/CVERecord?id=CVE-2023-50710) Hono: requests running at once could share route parameters.
- [CVE-2026-32887](https://www.cve.org/CVERecord?id=CVE-2026-32887) Effect: a request’s context could be lost, or mixed with another’s, inside fibers under load.
- [CVE-2026-59896](https://www.cve.org/CVERecord?id=CVE-2026-59896) Hono: hono/jsx didn’t keep context apart per request, so one request’s data could reach another’s page.
- Tested in [tests/security/cache-and-isolation.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/cache-and-isolation.test.ts).

## Bodies too big to read

A Worker shares its memory among the requests it serves, so one request that makes it read a huge body, or build thousands of error messages, slows or stops it for everyone. Frameworks have read bodies whole before anything could refuse them.

popweb reads at most 1 MB of a body unless an action asks for more, and Better Auth’s endpoints get the same limit. A body that declares a larger length is refused before a byte is read, and one sent without a length stops being read at the limit. A failed form lists at most 100 invalid fields, and a request of thousands of values stops at its first invalid one, so the answer is the same size whatever the count.

- [CVE-2024-47874](https://www.cve.org/CVERecord?id=CVE-2024-47874) Starlette: multipart form bodies were parsed without a limit.
- [CVE-2026-54283](https://www.cve.org/CVERecord?id=CVE-2026-54283) Starlette: form limits were ignored for urlencoded bodies.
- [CVE-2026-44456](https://www.cve.org/CVERecord?id=CVE-2026-44456) Hono: bodyLimit() could be passed by a body sent without a length.
- [CVE-2026-40073](https://www.cve.org/CVERecord?id=CVE-2026-40073) SvelteKit: the Node adapter’s body size limit could be passed.
- Tested in [tests/security/sizes.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/sizes.test.ts).

## Back after signing out

Inertia keeps each page’s data in the browser tab’s history, and Back shows it again without asking the server. After someone signs out, the next person at the same browser could page back through their account. Laravel apps guard against this with Inertia’s history encryption, cleared on sign-out. There’s no advisory id for this class: the precedent is how Laravel sets Inertia up.

popweb encrypts a signed-in person’s pages in the history by default, and the first page after the session ends clears them. The test ends the session every way it can end, by signing out, through Better Auth’s client, by being revoked elsewhere, by the cookie simply expiring, and by another member signing in, and checks that each clears the history or makes the browser ask the server. Tested in [tests/security/history.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/history.test.ts).

## Error detail in production

An error page that shows its message, its stack or its settings tells an attacker how your app is built, and sometimes hands over a secret.

In popweb an error in production shows no message, cause or stack to a browser or a JSON client, and nothing in the request, whether a query parameter, a header or a cookie, turns development on. The test checks development does show the detail, so its absence is production’s doing. Tools that collect errors, such as Sentry, get no submitted password, cookie or token, and no value from the address or Referer, such as a password-reset token.

- [CVE-2021-3129](https://www.cve.org/CVERecord?id=CVE-2021-3129) Laravel: debug mode’s error page, Ignition, could be reached and made to run code.
- [CVE-2024-52301](https://www.cve.org/CVERecord?id=CVE-2024-52301) Laravel: the environment, and so debug mode, could be set from the query string.
- [CVE-2019-5420](https://www.cve.org/CVERecord?id=CVE-2019-5420) Rails: the secret development mode generated could be guessed, letting an attacker run code.
- Tested in [tests/security/production.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/production.test.ts).

## The address sign-in limits count

Sign-in limits count guesses by the client’s address. If that address comes from X-Forwarded-For, which the client writes, every request can claim a new one.

popweb reads only Cloudflare’s CF-Connecting-IP, which a client can’t set; without it, the address is unknown, never a header’s. The test checks where the address comes from. Since 1.9.0 an IPv6 client counts by its network, its /64 unless an app gives another size, so it can’t send each guess from another of its addresses, which the Better Auth advisory is also about; the tests send guesses from one /64’s addresses until they’re refused.

- [CVE-2026-45364](https://www.cve.org/CVERecord?id=CVE-2026-45364) Better Auth: its rate limiter keyed on the X-Forwarded-For address as written, so IPv6 clients could rotate past it.
- [CVE-2026-27700](https://www.cve.org/CVERecord?id=CVE-2026-27700) Hono: behind AWS’s load balancer, its connection info took the first X-Forwarded-For address, which a client writes.
- Tested in [tests/security/production.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/production.test.ts).

## Slow regular expressions

Some regular expressions take exponentially longer as their input grows, so one long field can hold the thread that serves every request.

Twelve of popweb’s validators, from email and url to uuid, each get a hostile 20,000-character value and must answer within 50 milliseconds, and so must reading a media type and a route’s path. 1.5.0 made the email validators linear, where a 64 KB address had taken about 2.2 seconds, and a test checks they still accept and refuse exactly what they did.

- [CVE-2023-4316](https://www.cve.org/CVERecord?id=CVE-2023-4316) Zod: a regular expression could be made to backtrack exponentially.
- [CVE-2025-66020](https://www.cve.org/CVERecord?id=CVE-2025-66020) Valibot: its emoji regular expression could be made to backtrack exponentially.
- [CVE-2024-45296](https://www.cve.org/CVERecord?id=CVE-2024-45296) path-to-regexp: it produced regular expressions that backtrack, so a crafted path took exponential time.
- Tested in [tests/security/production.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/production.test.ts).

## Fixes in the libraries popweb uses

Some bugs live in the libraries popweb is built on. For those, the test doesn’t replay the attack: it checks that the versions popweb asks for start at the first release with the fix, Hono 4.13.5, Drizzle 0.45.2 and Better Auth 1.7.7, and leave out the last release without it.

Hono’s [CVE-2026-56763](https://www.cve.org/CVERecord?id=CVE-2026-56763), fixed in the same release, is listed under fields a form shouldn’t accept, where it’s also attacked directly.

- [CVE-2026-29086](https://www.cve.org/CVERecord?id=CVE-2026-29086) Hono: setCookie() didn’t sanitise domain and path, so attributes could be injected. Fixed in 4.13.5.
- [CVE-2026-47675](https://www.cve.org/CVERecord?id=CVE-2026-47675) Hono: the cookie helper didn’t sanitise sameSite and priority, so a Set-Cookie could be injected. Fixed in 4.13.5.
- [CVE-2026-84364](https://www.cve.org/CVERecord?id=CVE-2026-84364) Hono: unbounded dot-notation nesting in parseBody() could exhaust memory. Fixed in 4.13.5.
- [CVE-2026-39356](https://www.cve.org/CVERecord?id=CVE-2026-39356) Drizzle: SQL identifiers weren’t escaped properly, allowing SQL injection. Fixed in 0.45.2.
- [GHSA-965c-763c-88jm](https://github.com/advisories/GHSA-965c-763c-88jm) Better Auth: OAuth state could be used as a magic link to sign in as another user. Fixed in 1.7.7.
- Tested in [tests/security/production.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/production.test.ts).

## An account registered with someone else’s address

If anyone can sign up with an address they don’t own, and the owner later signs in by an emailed link, the impostor’s password, session or two-step challenge can outlive the owner’s arrival.

popweb’s Better Auth template signs no one in by password until their address is proven, and the tests replay the attack: once the owner signs in by a link, a two-step challenge the registrant holds, or a password sign-in they started, signs nobody in, and a phone number sent with the sign-up is refused. One risk remains, which the [auth docs](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/docs/auth.md#proving-an-address) describe: an owner who follows a verification link for an account they never created proves the address for whoever chose its password. These tests sit with the framework’s other Better Auth tests, and the suite’s README names them.

- [CVE-2026-53516](https://www.cve.org/CVERecord?id=CVE-2026-53516) Better Auth: an OAuth sign-in could link to an account someone had registered first with the owner’s unproven address.
- [GHSA-qq9h-g4jm-xgf3](https://github.com/advisories/GHSA-qq9h-g4jm-xgf3) Better Auth: someone who registered the owner’s address first kept password access after the owner began signing in by magic link or emailed code.
- [CVE-2022-35924](https://www.cve.org/CVERecord?id=CVE-2022-35924) NextAuth.js: a magic link could be sent to an unwanted address.
- Tested in [tests/better-auth/seam.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/better-auth/seam.test.ts).

## What isn’t covered yet

One class has no guard yet, and the suite says so: [gaps.test.ts](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/tests/security/gaps.test.ts) lists it as a todo, so every run shows it. It’s SQL whose names, rather than its values, come from the request, such as a column to sort by given to Drizzle’s sql.identifier(), .as() or orderBy. The todo is for a lint rule that reports such a value. Its precedents are SQL injection advisories in Django ([CVE-2022-28346](https://www.cve.org/CVERecord?id=CVE-2022-28346) and [CVE-2024-42005](https://www.cve.org/CVERecord?id=CVE-2024-42005)), Drizzle ([CVE-2026-39356](https://www.cve.org/CVERecord?id=CVE-2026-39356)) and Kysely ([CVE-2026-32763](https://www.cve.org/CVERecord?id=CVE-2026-32763)).

Drizzle’s own fix is in the release popweb asks for; what’s missing is the check on your code. Until it exists, if your app lets visitors choose a column to sort or filter by, match their choice against a fixed list of columns yourself.

The suite also can’t test your app’s own rules, such as who counts as an admin. [How the security works](/security#yours) lists what’s still yours.

## Run the suite yourself

To run it, clone the [framework’s repository](https://github.com/PatrickOgilvie/popcomputer-web) and run bun install. The command bun test ./tests/security/ then runs the suite: as of 1.9.0, 85 tests in 11 files, 84 passing and one the todo above. The command bun test tests/better-auth/seam.test.ts runs the account registration tests with the rest of the Better Auth ones, and bun run test runs everything the publish step does.

Each test file opens with the advisories it guards against, so you can check every line on this page against the test that backs it.

## Questions people ask

### Is @popcomputer/web secure?

No framework can promise that, and this one doesn’t. What it offers is evidence you can check: 70 published vulnerabilities from other frameworks and libraries, each turned into a test that runs before every release; two security audits, whose findings 1.5.0 and 1.6.0 fixed; and the protections [How the security works](/security) describes, on in every app by default. Your app’s own rules, such as who counts as an admin, are still yours to get right.

### Has @popcomputer/web been audited?

Twice. 1.5.0 fixed what a security audit of 1.4.2 found: one high, five medium and eleven low findings, and ten of its eleven leads; the eleventh needed no change. 1.6.0 fixed what a second audit found in 1.5.0’s changes: one medium and sixteen low findings, and six leads. Each fix was proven by re-running the audit’s own reproductions on the release before and the release after. 1.7.0 then fixed four more issues, found by the framework’s own fuzz tests and models of the state it keeps between requests. The [changelog](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/CHANGELOG.md) lists every one.

### What happens when a new vulnerability is published in another framework?

There’s no published policy or timetable for it. What the suite shows so far: a class the framework doesn’t yet guard is listed as a todo, so every run shows it, and becomes a test once the guard exists, as the body size limit and accounts registered with someone else’s address did in 1.5.0. When a library popweb builds on fixes a bug in a part it uses, the versions popweb asks for move to the fixed release, and a test checks them, as it does for Hono, Drizzle and Better Auth.

### Is @popcomputer/web production ready?

It’s at 1.9.0 and follows semantic versioning, and each release’s [changelog](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/CHANGELOG.md) entry says what an app needs to change, with an [upgrading guide](https://github.com/PatrickOgilvie/popcomputer-web/blob/main/UPGRADING.md) for each step. 1.6.0 shipped with 1,449 passing tests. 1.0 came out on 14 October 2026, so it’s new; Popcomputer and [The East Anglia Software Company](https://angliasoft.com) maintain and support it, and this site runs on it. Weigh that against what your app holds, and read the changelog’s security sections before you decide.

### Does a passing suite mean my app can’t be hacked?

No. A passing test shows popweb isn’t affected by that bug as it was written. It can’t show the framework has no bugs nobody has found yet, and it doesn’t test your app’s own rules, such as who may see an admin page. [Vibe coding safely](/vibe-coding-safely) covers how to check what your AI writes.

## Read next

- [How the security works.](https://web.popcomputer.com/security)
- [Can you vibe code safely?](https://web.popcomputer.com/vibe-coding-safely)
