Edge Functions security model
The rules that keep your functions, their code and their secrets private, and the places where you remain responsible.
1. Keys and roles
| Role | Key | Can do |
|---|---|---|
anon | Public key, safe in a browser | Invoke a function |
authenticated | Public key plus a signed-in user's JWT | Invoke a function, with the user's identity |
service_role | Secret key, server only | Everything: deploy, read code and secrets, jobs, cron, alerts |
admin | A console session in the Studio | Everything, from the Studio |
The role comes from the key and the token, which the platform validates. A client cannot choose it: a call with the public key and a header x-aura-role: service_role still reaches your function as anon.
Every route and its role is listed in the HTTP API reference.
2. Why only invocation is public
A public key is published in your web or mobile app, so anyone can read it. Whatever it allows, everyone is allowed. Invoking a function is the one operation designed for that: your code decides what a caller may do.
Reading the definition of a function is different. It contains the source code and the environment variables, which often hold secrets. Listing jobs and alerts shows your data. Enqueuing a job runs your code on your quota. None of these can be public, and the platform refuses them with 403 for any key that is not service_role.
The same split exists elsewhere. Supabase validates a user's JWT before an Edge Function runs by default (verify_jwt = true) and deploys functions through its CLI and platform (docs). Firebase enqueues tasks from a trusted server environment through the Admin SDK, with an IAM permission (docs). Cloudflare lets only a Worker send to a queue, through a binding (docs).
Earlier versions of the service answered GET /v1/functions and GET /v1/functions/{name} to any key. If your project ran on a deployment that predates this fix, treat the code and variables of your functions as exposed and rotate the secrets they contain. See upgrade to 0.6.
3. Identity cannot be forged
Your function reads the caller from x-aura-role, x-aura-user-id and x-aura-project-id. The platform sets them after it validates the key and the token.
- A caller cannot supply them. The platform discards
authorization,apikey,x-request-idand anyx-aura-*header sent in theheadersof an invocation, whatever their case. In a real test, a call withx-aura-role: service_roleand a forgedauthorizationreached the function asrole: "anon"with noauthorization. - Other custom headers pass through untouched. Treat them as user input.
- Jobs and cron firings reach your function without identity headers. They are triggered by the platform, not by a user.
So authorise on x-aura-role and x-aura-user-id, never on a value from the body:
4. The sandbox
What the platform gives you:
- Each call runs in its own V8 isolate, with its own memory limit and wall-clock timeout. A crash or an infinite loop in one call does not affect another.
- The host's environment variables are invisible:
Deno.env.get('PATH')returnedundefinedin a real run. Only your function's variables and the platform'sAURABASE_*variables are present. - The code of one project cannot be read by another: the service that serves it requires an internal token.
- Responses that can run scripts are served with a sandboxing content security policy, and
Set-Cookieand CORS headers from a function are dropped. See responses.
What it does not give you:
- Outbound network is open unless you restrict it. When
permissions.allow_netlists at least one host, the runtime refuses afetchto any other host (Requires net access to "host:443"). An empty list, the default, restricts nothing. List every host the function needs, including your project URL if it calls the platform. Even with a list, validate any URL taken from user input. - No protection of your own secrets in logs. Anything your function writes with
console.loggoes to the runtime's logs. - No limit per user. The invocation rate limit is per client IP. Add your own check if one user must not call a function more than a few times.
5. Checklist
- The
service_rolekey exists only on your servers and in your CI secrets. - The browser bundle contains the public key only. The SDK refuses a
service_rolekey in a browser by default. - Every function checks
x-aura-rolebefore it does anything sensitive. - Secrets are variables of the function, never in
index.ts. - Functions that call external URLs validate the destination.
- You rotate a key by creating a new one in the Studio, deploying it as a secret, then revoking the old one.