Files
supabase/apps/docs
Miranda Limonczenko 17512de71d docs(database): connect security definer to the default execute grant (#50817)
Closes DOCS-1319

Part 4 of 4 in stack #50823. This PR carries **additions**: content the
page never had.
Style, structure, and snippet fixes land below it in #50820, #50821, and
#50822.

## Problem

Developers and AI agents read the Database functions guide. The guide
shows how to write a function inside the database.

A function runs in one of two modes. In `invoker` mode it runs as the
caller. In `definer` mode it runs as the creator.

The guide gives one rule for `definer` mode. The rule is to set the
`search_path`. A reader who obeys that rule still gets an unsafe
function.

I wrote a function that obeys the rule. I pinned the search path to the
empty string. Then I called it three times.

| Caller | Result |
| --- | --- |
| The order's owner | 4330 cents, correct |
| A different signed-in customer | 8660 cents, another customer's order
|
| Nobody, no session at all | 8660 cents |

Every role can call a new function in `public`. The guide states that
fact under Function privileges. It never connects the fact to the
`definer` rule. A reader has no reason to look.

Customers report the same failure. AI tools choose `definer` mode. The
function then answers the front end with no session. The hole is hard to
find, because no policy is involved in it.

## Solution

- **Joins the two halves in the Security definer subsection.** A
`danger` admonition states three facts:
  - The function runs with its creator's privileges.
- A function created in the Dashboard or by a migration is owned by
`postgres`, which bypasses Row Level Security.
  - Every role can call the function by default.

The admonition then gives the fix. Check ownership inside the function
body, and narrow the execute privilege as well.

- **Applies the regrant to both ways of restricting execute.** The
`grant execute` block sat inside the second way. A reader who took the
first way saw no way to restore their own app's access.

**Not changed:** the existing definer paragraph, the page structure, and
the Function privileges statements. The lower PRs in the stack own
those.

**No eval re-run.** The guide scored 6 of 6 on three baseline runs, so
the score has no room to move. All three runs chose `invoker` mode. The
eval never enters the branch this PR fixes. Measuring it needs a new
check, not a re-run.

**Diff size:** one file, 15 lines added and 4 removed.

## Manual testing

1. Open the [Security definer vs invoker
section](https://docs-git-docs-definer-function-privileges-supabase.vercel.app/docs/guides/database/functions#security-definer-vs-invoker)
on the deploy preview. The admonition renders as a red `danger` panel
below the definer paragraph. The two fixes appear as bullets.
2. Click the `Function privileges` link inside the admonition. It jumps
to the [Function privileges
section](https://docs-git-docs-definer-function-privileges-supabase.vercel.app/docs/guides/database/functions#function-privileges)
on the same page.
3. Read that section. The `grant execute` block sits after the numbered
list. It applies to both ways of restricting execute.
4. Run `pnpm build:guides-markdown` from `apps/docs`. Read
`apps/docs/public/markdown/guides/database/functions.md`. The admonition
appears as a `Danger:` paragraph. Discard the `manifest.json` change.
5. Run `npx prettier --check
apps/docs/content/guides/database/functions.mdx`. Clean.



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Expanded guidance on the security risks of `security definer`
functions, including their privileges, interaction with Row Level
Security, and default execution access.
* Added recommendations for checking data ownership and restricting
execution to intended roles.
* Clarified that pinning `search_path` does not limit execution
privileges.
* Presented the function-privileges example separately from the
default-privileges instructions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

## Preview links

| Site | Live | Preview | Search for |
| --- | --- | --- | --- |
| Docs |
[/docs/guides/database/functions](https://supabase.com/docs/guides/database/functions)
|
[/docs/guides/database/functions](https://docs-git-docs-definer-function-privileges-supabase.vercel.app/docs/guides/database/functions)
| `every signed-in caller` |

## Review instructions

This PR adds one admonition and moves one code block. The question is
whether the admonition is correct and whether an agent reading the page
would act on it.

1. Open the preview at [Security definer vs
invoker](https://docs-git-docs-definer-function-privileges-supabase.vercel.app/docs/guides/database/functions#security-definer-vs-invoker).
A red `danger` panel sits below the definer paragraph.
2. **Read the first paragraph for accuracy.** It claims the function
runs with its creator's privileges, that a function created in the
Dashboard or by a migration is owned by `postgres`, and that `postgres`
bypasses Row Level Security. This matches [Use security definer
functions](https://supabase.com/docs/guides/database/postgres/row-level-security#use-security-definer-functions)
in the RLS guide. Flag any drift.
3. **Read the two bullets for sufficiency.** The ownership check is the
primary fix. The execute grant is listed as a complement, not an
alternative, because granting to `authenticated` still returns any
user's row to every signed-in caller. Confirm the wording can't be read
as "either one is enough."
4. Click the `Function privileges` link inside the admonition. It jumps
down the same page.
5. In the Function privileges section, confirm the `grant execute` block
sits after the numbered list rather than inside item 2, so it applies to
both ways of restricting execute.
6. **Check the agent-facing copy.** Open [the markdown
export](https://docs-git-docs-definer-function-privileges-supabase.vercel.app/docs/guides/database/functions.md)
and find `Danger:`. This is what an agent reads, and it is the audience
this PR exists for.

**If you only have two minutes:** do steps 3 and 6. Step 3 is the
correctness of the advice. Step 6 is whether the audience that prompted
the ticket actually receives it.

**A note on running SQL from this page.** Don't hand-paste from the
rendered page. Blocks are split across tabs, and the Data tab in
Returning data sets holds markdown tables that look pasteable but are
not SQL. Use the `.md` export of the page, which flattens every tab in
page order. #50822 has a copy-paste command for this.
2026-10-05 15:18:01 -07:00
..
2026-07-01 12:59:00 +02:00
2026-10-02 12:33:25 -05:00

Reference Docs

Supabase Reference Docs

Maintainers

If you are a maintainer of any tools in the Supabase ecosystem, you can use this site to provide documentation for the tools & libraries that you maintain.

DocSpec

We use documentation specifications which can be used to generate human-readable docs.

  • OpenAPI: for documenting API endpoints.
  • SDKSpec (custom to Supabase): for SDKs and client libraries.
  • ConfigSpec (custom to Supabase): for configuration options.
  • CLISpec (custom to Supabase): for CLI commands and usage.

The benefit of using custom specifications is that we can generate many other types from a strict schema (eg, HTML and manpages). It also means that we can switch to any documentation system we want. On this site we use Next.js, but on Supabase's official website, we use a custom React site and expose only a subset of the available API for each tool.

Contributing

To contribute to docs, see the style guide for how to write a page, and the developers' guide and contributing guide for repo mechanics. If you write with an AI coding agent, use the /write-the-docs skill to draft and /edit-the-docs to revise an existing page.