/safe-sql-execution
Use whenever code will build, return, fetch, or execute SQL that runs against a user's real Postgres database — even when the request reads like an ordinary feature or bug fix and never says "security," "injection," or "SafeSqlFragment." This covers: writing or editing any
$ npx -y skills add supabase/supabase --skill safe-sql-execution --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/safe-sql-execution
Context preview
The summary Claude sees to decide when to auto-load this skill.
Use whenever code will build, return, fetch, or execute SQL that runs against a user's real Postgres database — even when the request reads like an ordinary feature or bug fix and never says "security," "injection," or "SafeSqlFragment." This covers: writing or editing any
SKILL.md
safe-sql-execution.SKILL.mdname: safe-sql-execution
description: >-
Use whenever code will build, return, fetch, or execute SQL that runs against
a user's real Postgres database — even when the request reads like an ordinary
feature or bug fix and never says "security," "injection," or
"SafeSqlFragment." This covers: writing or editing any pg-meta function, query
builder, or endpoint that builds/returns SQL for database objects (tables,
views, functions, DB triggers, indexes, RLS policies); interpolating a
schema/table/column/search/route-param value into SQL text; storing, fetching,
or re-running SQL that round-trips from the database (a policy's definition, a
function/view definition, a snippet's saved content); and any
"Run"/"Apply"/"Execute" action that sends SQL to a project's database (SQL
editor run-selection, policy editor apply, snippet runner). Load this BEFORE
writing such code, not only when reviewing a finished diff. Skip only for
changes that never touch SQL text or execution — styling, unrelated data
hooks, non-SQL form validation, or UI layout work.
Safe SQL execution
Supabase Studio executes SQL statements directly against the user's database. Because this is the authenticated user's own database, our security model is different from most frontend applications: a user should be able to execute any SQL statement, as long as it is proven that they themselves authored it. What we SHOULD NOT ALLOW is execution of SQL statements that can be influenced by an attacker, such as through URL parameters.
Security model
The security model for SQL execution in Supabase Studio is based on the principle of "proven authorship". This means that a user should only be able to execute SQL statements that they have explicitly authored, and not statements that can be influenced by external input.
There are three classes of SQL fragments:
1. Hardcoded within the application code. These are safe to execute because they cannot be influenced by an attacker. They can be marked with the `safeSql` utility with `pg-meta`:
import { safeSql } from '@supabase/pg-meta'
const sql = safeSql`
SELECT *
FROM users
WHERE id = 1
``safeSql` automatically creates a string of the branded type `SafeSqlFragment`. (See Provenance Tracking below.)
2. Third-party influenceable. These are SQL fragments that can be influenced by an attacker, such as through URL parameters or LLM output. These should be marked with the `untrustedSql` utility with `pg-meta`:
import { untrustedSql } from '@supabase/pg-meta'
const unsafeQuery = searchParams.get('query')
const querySql = untrustedSql(unsafeQuery)`untrustedSql` creates a string of the branded type `UntrustedSqlFragment`. (See Provenance Tracking below.)
3. User-authored. These are SQL fragments that are authored by the user themselves within the UI, for example in a text input field. Because the user is the author, these should be considered safe to execute.
However, there is a caveat, where third-party and user-authored code can mix, contaminating the user-authored code (for example, if an input is prefilled from an unsanitized URL parameter). Provenance tracking helps us track these cases.
For example, a safe input component could be implemented as follows by requiring that its placeholder and controlled value are of type `SafeSqlFragment`. In this case we can use its onChange to promote the user input to `SafeSqlFragment` type, because we know that the user is the author of the input. An implementation of this is in @apps/studio/components/ui/SafeSqlInput.tsx:
import { rawSql, type SafeSqlFragment } from '@supabase/pg-meta'
import type { ChangeEvent, ComponentProps } from 'react'
import { Input } from 'ui-patterns/DataInputs/Input'
type InputProps = ComponentProps<typeof Input>
export type SafeSqlInputProps = Omit<
InputProps, 'placeholder' | 'value' | 'onChange'
> & {
placeholder?: SafeSqlFragment
value: SafeSqlFragment
onChange?:
(event: ChangeEvent<HTMLInputElement>, value: SafeSqlFragment) => void
}
export const SafeSqlInput = ({ onChange, ...props }: SafeSqlInputProps) => (
<Input
{...props}
onChange={(event) => onChange?.(event, rawSql(event.target.value))}
/>
)This is pretty much the ONLY VALID USE CASE of the rawSql export from pg-meta, and it should be used with caution.
Provenance tracking
Branded types are used to track the provenance of SQL fragments. The types, exported from `pg-meta`, are:
- `SafeSqlFragment`: represents SQL fragments that are safe to execute, because
they are either hardcoded in the application or authored by the user themselves.
- `UntrustedSqlFragment`: represents SQL fragments that can be influenced by an
attacker, such as through URL parameters or LLM output.
These are valid ways to generate a `SafeSqlFragment`:
- Using the `safeSql` utility from `pg-meta` to create hardcoded SQL fragments.
- Using the sanitization utilities from `pg-meta` to sanitize untrusted input
and promote it to a `SafeSqlFragment`:
- `ident`
- `literal`
- `keyword`
- Using the safe SQL manipulation utilities:
- `joinSqlFragments`
- `trimSafeSqlFragment`
`UntrustedSqlFragments` can be generated from raw strings using `untrustedSql()`.
There is also a union type, `DisplayableSqlFragment`, which represents SQL fragments that can be safely displayed in the UI, but not necessarily executed. This includes both `SafeSqlFragment` and `UntrustedSqlFragment`.
Security of SQL round-tripped from the user's database
SQL derived directly from catalog tables (e.g., function definitions, RLS expressions, etc.) is considered safe, and it is promoted AT THE POINT OF BEING QUERIED from the database. In most cases, this is in an apps/studio/data/\*_/_.t
Read more
name: safe-sql-execution description: >- Use whenever code will build, return, fetch, or execute SQL that runs against a user's real Postgres database — even when the request reads like an ordinary feature or bug fix and never says "security," "injection," or "SafeSqlFragment." This covers: writing or editing any pg-meta function, query builder, or endpoint that builds/returns SQL for database objects (tables, views, functions, DB triggers, indexes, RLS policies); interpolating a schema/table/column/search/route-param value into SQL text; storing, fetching, or re-running SQL that round-trips from the database (a policy's definition, a function/view definition, a snippet's saved content); and any "Run"/"Apply"/"Execute" action that sends SQL to a project's database (SQL editor run-selection, policy editor apply, snippet runner). Load this BEFORE writing such code, not only when reviewing a finished diff. Skip only for changes that never touch SQL text or execution — styling, unrelated data hooks, non-SQL form validation, or UI layout work.
Safe SQL execution
Supabase Studio executes SQL statements directly against the user's database. Because this is the authenticated user's own database, our security model is different from most frontend applications: a user should be able to execute any SQL statement, as long as it is proven that they themselves authored it. What we SHOULD NOT ALLOW is execution of SQL statements that can be influenced by an attacker, such as through URL parameters.
Security model
The security model for SQL execution in Supabase Studio is based on the principle of "proven authorship". This means that a user should only be able to execute SQL statements that they have explicitly authored, and not statements that can be influenced by external input.
There are three classes of SQL fragments:
1. Hardcoded within the application code. These are safe to execute because they cannot be influenced by an attacker. They can be marked with the `safeSql` utility with `pg-meta`:
import { safeSql } from '@supabase/pg-meta'
const sql = safeSql`
SELECT *
FROM users
WHERE id = 1
``safeSql` automatically creates a string of the branded type `SafeSqlFragment`. (See Provenance Tracking below.)
2. Third-party influenceable. These are SQL fragments that can be influenced by an attacker, such as through URL parameters or LLM output. These should be marked with the `untrustedSql` utility with `pg-meta`:
import { untrustedSql } from '@supabase/pg-meta'
const unsafeQuery = searchParams.get('query')
const querySql = untrustedSql(unsafeQuery)`untrustedSql` creates a string of the branded type `UntrustedSqlFragment`. (See Provenance Tracking below.)
3. User-authored. These are SQL fragments that are authored by the user themselves within the UI, for example in a text input field. Because the user is the author, these should be considered safe to execute.
However, there is a caveat, where third-party and user-authored code can mix, contaminating the user-authored code (for example, if an input is prefilled from an unsanitized URL parameter). Provenance tracking helps us track these cases.
For example, a safe input component could be implemented as follows by requiring that its placeholder and controlled value are of type `SafeSqlFragment`. In this case we can use its onChange to promote the user input to `SafeSqlFragment` type, because we know that the user is the author of the input. An implementation of this is in @apps/studio/components/ui/SafeSqlInput.tsx:
import { rawSql, type SafeSqlFragment } from '@supabase/pg-meta'
import type { ChangeEvent, ComponentProps } from 'react'
import { Input } from 'ui-patterns/DataInputs/Input'
type InputProps = ComponentProps<typeof Input>
export type SafeSqlInputProps = Omit<
InputProps, 'placeholder' | 'value' | 'onChange'
> & {
placeholder?: SafeSqlFragment
value: SafeSqlFragment
onChange?:
(event: ChangeEvent<HTMLInputElement>, value: SafeSqlFragment) => void
}
export const SafeSqlInput = ({ onChange, ...props }: SafeSqlInputProps) => (
<Input
{...props}
onChange={(event) => onChange?.(event, rawSql(event.target.value))}
/>
)This is pretty much the ONLY VALID USE CASE of the rawSql export from pg-meta, and it should be used with caution.
Provenance tracking
Branded types are used to track the provenance of SQL fragments. The types, exported from `pg-meta`, are:
- `SafeSqlFragment`: represents SQL fragments that are safe to execute, because
they are either hardcoded in the application or authored by the user themselves.
- `UntrustedSqlFragment`: represents SQL fragments that can be influenced by an
attacker, such as through URL parameters or LLM output.
These are valid ways to generate a `SafeSqlFragment`:
- Using the `safeSql` utility from `pg-meta` to create hardcoded SQL fragments.
- Using the sanitization utilities from `pg-meta` to sanitize untrusted input
and promote it to a `SafeSqlFragment`:
- `ident`
- `literal`
- `keyword`
- Using the safe SQL manipulation utilities:
- `joinSqlFragments`
- `trimSafeSqlFragment`
`UntrustedSqlFragments` can be generated from raw strings using `untrustedSql()`.
There is also a union type, `DisplayableSqlFragment`, which represents SQL fragments that can be safely displayed in the UI, but not necessarily executed. This includes both `SafeSqlFragment` and `UntrustedSqlFragment`.
Security of SQL round-tripped from the user's database
SQL derived directly from catalog tables (e.g., function definitions, RLS expressions, etc.) is considered safe, and it is promoted AT THE POINT OF BEING QUERIED from the database. In most cases, this is in an apps/studio/data/\*_/_.t
Supabase is the Postgres development platform. We're building the features of Firebase using enterprise-grade open source tools. [x] Hosted Postgres Database. Docs [x] Authentication and Authorization. Docs [x] Auto-generated APIs. [x] REST. Docs [x] GraphQL.
Repo: supabase/supabase
Other skills on supabase.
- /clickhouse-logs-queries
Write, review, and migrate Supabase logs queries against the ClickHouse-backed `logs` table (the `logs.all.otel` analytics endpoint). Use this whenever a task involves Logs Explorer SQL, the `log_attributes` map, querying a log `source` (edge_logs, postgres_logs, auth_logs,
Open skill - /copywriting
Write or audit UI copy (buttons, labels, empty states, error messages, tooltips, form text) anywhere in the monorepo. Load it before shipping or reviewing any user-facing text — including when copy is incidental to the task, like a new feature that adds buttons, toasts, dialogs,
Open skill - /dev-toolbar-review
Safety rules for the dev toolbar, PostHog client, and feature flags. Use
Open skill - /docs-content
Write, edit, organize, and review Supabase content anywhere in apps/docs — guides, explainers, tutorials, troubleshooting entries, reference docs, and partials. Use for MDX/TOML authoring, frontmatter, navigation, terminology, links, code samples, content listings, and docs
Open skill - /react-hook-form
Correct React Hook Form usage anywhere in the monorepo — data flow, subscriptions,
Open skill - /studio-e2e-tests
Write and run Playwright E2E tests for Supabase Studio (e2e/studio).
Open skill

