· via dev.to (home feed)
Neon Data API and Postgres RLS survive 27-attack audit, with a 15-minute revocation gap
A dev.to audit threw 27 scripted attacks at a backend-less app built on Neon's Data API and Postgres row-level security. All were blocked, but removed team members kept access for about 15 minutes.

A write-up on dev.to by The DevOps Daily documents a security audit of an increasingly common architecture: an application with no backend code, where the browser talks to a managed database endpoint and Postgres enforces every access rule. The team built a multi-tenant team task board from static files, Neon Auth and the Neon Data API, then tried to break into it 27 different ways.
How the app works without a server
According to the article, the browser loads static files, signs in through Neon Auth and issues every query through the Neon Data API, a PostgREST-compatible endpoint that verifies the JWT on each request and runs the query under an authenticated role. There is no API server, no serverless function and no middleware.
Teams map to Neon Auth organizations. When a user activates one, the issued token names it in a claim, and Neon Auth refuses to issue a token for an organization the user does not belong to. Inside Postgres, an auth function reads that claim, table defaults stamp it into an org_id column, and every row-level security policy compares against it. A separate policy restricts deletes to users whose signed claim carries the owner or admin role.
Column grants form a second defensive layer. The authenticated role can insert and update only the title and done columns of the tasks table; org_id and created_by are filled by defaults derived from the token, so a client can never move a row into another tenant. Everything is revoked from the anonymous role. One dashboard function runs as security definer with a WHERE clause pinned to the caller's organization, and its execute privilege is revoked from PUBLIC.
Twenty-seven attacks, twenty-seven refusals
The audit script signs in three users: alice, owner of the target team; bob, a member of it; and eve, owner of a rival team with a perfectly valid account. Eve then attempts 25 routes into the first team plus two privilege attempts from the wrong role inside it. The list includes crafted filters, embedded joins, aggregate counts, forged tokens, an alg:none token, a token signed with her own key, bulk updates and upserts targeting the other team's row ids. All 27 were refused.
The verification is stricter than a status-code check: each attack must return exactly the expected response, and every column of the victim's rows is compared before and after, so a rejection that quietly modified data would still fail the test.
Breaking it on purpose
The team then introduced four realistic misconfigurations one at a time. Three of them let specific attacks through, and in each case the suite flagged the regression. The fourth, a careless WITH CHECK (true) on an insert policy, leaked nothing on its own, because the grants layer had never given the client access to the org_id column in the first place. It only became exploitable once broader table-wide grants were layered on top, which the authors identify as the real mistake.
The one thing the policies could not stop: time
Removing a member does not invalidate the token already sitting in their browser. In the test, a freshly removed member could still read, write and call the dashboard function, and kept reading for roughly fifteen and a half minutes, the token's 900-second lifetime plus about 28 seconds.
The fix the authors demonstrate is to consult Neon Auth's membership table from inside the policies, which cut access off immediately. The median cost was under a millisecond for reads and writes and around 5 ms extra for the function call. Overall latency stayed unremarkable: Data API requests measured 41 to 44 ms at the median against a 40 ms bare TCP connect to the same address, so nearly all of the round trip is network, not authorization.
Why it matters
The backend-less pattern, a static front end plus a PostgREST-style data API plus row-level security, is spreading because it deletes an entire class of server code and the bugs that come with it. This audit is a rare empirical check of the pattern rather than a theoretical argument, and its two headline findings apply well beyond Neon. First, layered defenses work: a policy bug was contained by column grants that made the tenant column untouchable. Second, JWT-based authorization always carries a revocation window equal to the token lifetime, and pushing a membership check into the database closes that window for about a millisecond. The full SQL and attack scripts are published on GitHub as The-DevOps-Daily/neon-data-api-rls for anyone running, or considering, a similar stack.
- #postgres
- #row-level-security
- #neon
- #security
- #cloud