Unauthenticated edge function
Part of the Auth hardening & exposure config check · fix arrives as a guide
What it is
An edge function is deployed with verify_jwt disabled, so it accepts requests without a valid Supabase JWT.
Why it matters
The function URL is public and guessable, and the function itself typically holds service credentials. Anything it does becomes available to anyone who finds the endpoint.
Check whether a function is open right now
Call it with no Authorization header. A protected function answers 401 before your code runs; an open one executes:
curl -i https://YOUR-PROJECT.supabase.co/functions/v1/my-function
# verify_jwt on → HTTP 401 {"code":401,"message":"Missing authorization header"}
# verify_jwt off → HTTP 200, and your function just ran for an anonymous callerWhat turns verification off
JWT verification is on by default and gets disabled one of three ways — a deploy flag, a config line, or the dashboard toggle. The setting sticks with the function after a single flagged deploy, which is how it survives long after the reason for it is forgotten:
# 1 — deploy flag
supabase functions deploy my-function --no-verify-jwt
# 2 — supabase/config.toml
[functions.my-function]
verify_jwt = false
# 3 — Dashboard → Edge Functions → my-function → Details → Enforce JWT verification (off)Why an open function is worse than an open route
Edge functions are where service credentials live — SUPABASE_SERVICE_ROLE_KEY is injected into every function’s environment by default, and functions typically use it to bypass RLS on purpose. An unauthenticated function that touches the database with the service key hands that privilege to any caller. The URL is guessable (/functions/v1/ plus a name), enumerable, and CORS does not protect it — CORS constrains browsers, not curl.
Fix it manually
For functions only your app calls, re-enable verification and pass the user’s JWT through:
# config.toml — make it explicit and version-controlled
[functions.my-function]
verify_jwt = true
# then redeploy
supabase functions deploy my-function
// client — supabase-js attaches the session JWT automatically
const { data, error } = await supabase.functions.invoke('my-function')Webhook receivers — Stripe, GitHub, Resend — legitimately cannot present a Supabase JWT. Leave verify_jwt = false for those, and verify the provider’s signature before doing any work: stripe.webhooks.constructEvent(body, sig, secret) or the equivalent, with the raw request body, rejecting on failure. A webhook function with neither JWT verification nor signature verification is simply an open endpoint.
How lumioguard fixes it
The scan lists every deployed function with verify_jwt off, separates the ones that look like webhook receivers from the ones that do not, and flags any open function whose code reaches for the service-role key. The fix arrives as a step-by-step guide per function — which to re-protect, which to leave open with a signature check, and the exact config and code for each.
Run them all on your app
Connect your repo and your live services with read-only scopes. The first scan is free, and nothing changes without your approval.