feat: eval the API keys guide in supabase.com/docs - #212
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
czenko
force-pushed
the
docs/api-key-eval
branch
from
August 24, 2026 16:59
82f8870 to
c64044c
Compare
czenko
force-pushed
the
core/share-docs-guide-read-check
branch
from
August 24, 2026 17:09
53a5412 to
8c98b7c
Compare
czenko
force-pushed
the
docs/api-key-eval
branch
2 times, most recently
from
August 24, 2026 17:12
cf1c7a9 to
89c0ffa
Compare
czenko
commented
Aug 24, 2026
czenko
force-pushed
the
docs/api-key-eval
branch
from
August 25, 2026 17:20
8b6cb00 to
78ee2ac
Compare
Adds `ctx.stackStatus()` to the local-stack scoring context, returning the stack's url plus its publishable, secret, and legacy anon keys. `getClient()` only carries the publishable key, so a scorer that needs the others had to parse `supabase status` a second time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds `build-docs-003-api-keys-guide`. The guide is the subject under test: the prompt is a product request plus the guide's url, and never says key, publishable, secret, service role, server, expose, or environment variable. Seeds a Vite app whose roster screen needs an address that no publishable key reaches, so the convenient answer ships the whole table to the browser. Scores nine checks across the built bundle, the client source, the running stack, and whether the guide was retrieved at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
czenko
force-pushed
the
docs/api-key-eval
branch
from
August 25, 2026 17:25
78ee2ac to
34e27d3
Compare
Collaborator
|
(Merging main in so that it can use the new 3-run averaging strategy) |
mattrossman
reviewed
Aug 26, 2026
mattrossman
reviewed
Aug 26, 2026
mattrossman
reviewed
Aug 26, 2026
mattrossman
approved these changes
Aug 26, 2026
mattrossman
left a comment
Collaborator
There was a problem hiding this comment.
Looks good overall and tested a couple runs on my end.
I'd just call attention to this potential flakiness I experienced a couple times.
…in one place The roster probe called the endpoint with the publishable key as its bearer token, so a roster gated on being signed in returned 401 and failed a check about whether the roster works. It now sends the fixture user's access token, which a roster open to anyone answers too. `checkBundle` and the access probes return keyed check records, so `EVAL.ts` lists all nine checks in one array instead of spreading them in from two files. A seed that fails now reports both access checks as unproven rather than replacing them with a single failure, keeping the check count stable. `no secret-bearing env var is client-exposed` reads Vite's inlined prefixes off `vite.config.ts`, so a renamed `envPrefix` stays in range of the check.
The bundle check accepted the legacy anon key alongside the publishable key, so a run that reached for the deprecated format scored clean. `client uses a publishable key, not the legacy anon key` now makes that a separate claim, leaving the existing check as the control on whether the client is wired up. The complaints behind it are FDBKIN-19189, FDBKIN-32569, FDBKIN-6545, and DOCS-313, all users who could not tell which key format to reach for. The server side stays unscored. The Edge Function runtime injects SUPABASE_SERVICE_ROLE_KEY, so a correct implementation still holds a legacy credential there, and failing that would measure a platform default.
The Edge Function runtime fills SUPABASE_SERVICE_ROLE_KEY and SUPABASE_ANON_KEY with legacy keys, so keeping the secret off the client is not the same as using the new key format on the server. `server reads no legacy key variable` scores that separately. The guide says to use the publishable and secret keys, so a server holding a deprecated credential is a gap in the guide rather than a reasonable answer. Scored as a source scan because both formats resolve to the same Postgres role, leaving the source as the only place the difference shows. Measured on the pinned CLI: the runtime injects no new-format key, so a function has to supply its own. Reading the secret key from its own name in `supabase/functions/.env` works and passes every other check. README records the working implementation and the CLI the behaviour depends on. Splits `walk` and `readText` into `files.ts`, now that the bundle scan and the server scan both need them.
The eval inherited the sandbox default of 2.67.1, whose Edge Function runtime injects only legacy keys. A key-format eval cannot fairly score the server side on a runtime with no new-format key to offer, so pin 2.109.1 the way the other key evals do. Its runtime injects SUPABASE_SECRET_KEYS. README now states the passing shapes for that runtime.
…rmat keys" This reverts commit ad96ec7.
barryroodt
reviewed
Sep 1, 2026
barryroodt
left a comment
Contributor
There was a problem hiding this comment.
Looking good, I left a few inline comments on scorer cases that can still pass when they should fail.
The client env scan skipped `export VITE_ADMIN=sb_secret_...`, because the `export ` prefix broke the name match, and skipped a secret behind a prefix declared through a variable, because only a literal `envPrefix` parsed. It now reads every `.env` outside `supabase/` and fails on a secret in any of them, parsing neither names nor prefixes. A secret in the client's env is exposed whichever name holds it. The server check matched variable names only, so a legacy key bound to an alias passed. It now also decodes JWTs under `supabase/functions` and fails on an `anon` or `service_role` role. Renamed to `no legacy key reaches the server`, which is what it now proves. `client source calls signUp` overstated a literal match, since dead code satisfies it. Renamed to `client source contains a signUp call`. Proving the screen works needs a driven DOM the scorer does not have, so the claim is narrowed rather than strengthened. Credential detection moves to keys.ts, now that the bundle scan and the server scan both need it.
Two checks measured the platform rather than the page, so a CLI change would report as a docs change. `client uses a publishable key, not the legacy anon key` could not observe its own condition. The build injects a placeholder over VITE_SUPABASE_ANON_KEY, so a client wired through that name carries no real key into the bundle. `no legacy key reaches the server` scored which keys the Edge Function runtime hands a function. The pinned CLI hands over legacy ones only, so a function reading them follows the runtime, not the guide. Nine checks remain. The positive controls stay: without them a run that built nothing scores full marks.
Contributor
Author
|
Update: I added a couple more checks since Matt's review and have since reverted them since they proved to not reflect on the document but on the functionality of the CLI. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes DOCS-1301
Problem
We point agents at the API keys guide. Nobody has measured whether reading it is enough to keep the secret key out of a browser bundle.
No existing eval answers that.
resolve-sdk-001-legacy-key-migrationis Node scripts, server-side throughout.build-functions-004/005/006are Edge Function auth, where the secret key is legitimately server-side. None of them builds a client bundle, so none can catch a secret key shipped to a browser.Solution
build-docs-003-api-keys-guide. The guide is the subject under test. The prompt is a product request plus the guide's url, and never says key, publishable, secret, service role, server, expose, or environment variable.auth.usersand no publishable key reaches. The roster is the one place where the convenient answer ships the whole table to the browser.ctx.stackStatus()to the local-stack scoring context, which reports the stack's url plus its publishable, secret, and legacy anon keys. The scorer needs all four, andgetClient()only carries the publishable key.Checks that exist to stop a false green
roster returns every signed-up emailpairs with the bundle checks, which a run that built nothing passes trivially.client source contains a signUp callpairs withclient bundle carries a publishable or anon key, which passes for a key that ships and is never called.no client role can read another user's emailcatches the design that dodges the elevated call entirely by copying addresses into a table anyone can read. Everything else scored green before this check existed.Where the email check came from
Not from the page under test. The page frames publishable-key safety as an RLS checklist, and the shortcut of copying addresses into a readable table satisfies that checklist while leaking. It came from
api/securing-your-api, which treats grants and RLS as separate layers.What this eval does not score
Two checks were tried and removed, because both measured the platform rather than the page. Keeping either means a CLI upgrade reports as a docs change.
VITE_SUPABASE_ANON_KEY, so a client wired through that name never carries a real key into the bundle and the format is not observable.Known limitations
auth.admin.listUsers()defaults to 50 per page and PostgREST to 1000 rows, so this is a narrow edge, but it would surface as an unexplained red.no secret-bearing env var is client-exposedpasses by design. It reads every.envoutsidesupabase/and fails on a secret in any of them. An agent that keeps the secret in the function env passes, and that is the intended outcome.client source contains a signUp callis a literal match. Dead code satisfies it, and a call reached only from outside client source does not. Proving the screen works needs a driven DOM the scorer does not have.build-rls-003-org-roles-permissionsmeasures.suite: regression, not benchmark. It is a regression detector for the guide, and it reports no failure against the published page.Baseline
claude-code-sonnet-5with and without skills, 3 runs each, CI run 33549257417: 9/9 on all six runs. Results are committed to this branch.No check fails against the published guide, which is the floor a regression detector needs. A later docs change that breaks key placement shows up as a drop from this.
The scorer was also pointed at a revised draft of the guide, and that came back 9/9 on all six runs too, so the draft introduces no regression.
Manual testing
docker ps --format '{{.Names}}\t{{.Ports}}' | grep -E '5432[12]->'and stop whatever holds them.pnpm eval:dry -- --eval build-docs-003-api-keys-guide. It plans 18 experiments.pnpm typecheck. Clean.README.mdin the eval folder. Confirm the persona rules and the check rationale match the scorer.