Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ updates:
patterns:
- "*"
- package-ecosystem: "docker"
directory: "pkg/config/templates"
directory: "/apps/cli-go/pkg/config/templates"
schedule:
interval: "cron"
cronjob: "0 0 * * *"
Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/cli-go-mirror-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ on:
description: "org/image:tag"
required: true
type: string
paths:
- apps/cli-go/**

permissions:
contents: read
Expand Down Expand Up @@ -50,6 +48,3 @@ jobs:
dst: |
public.ecr.aws/supabase/${{ steps.strip.outputs.image }}
ghcr.io/supabase/${{ steps.strip.outputs.image }}
defaults:
run:
working-directory: apps/cli-go
5 changes: 4 additions & 1 deletion apps/cli-go/internal/db/reset/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,10 @@ func initDatabase(ctx context.Context, options ...func(*pgx.ConnConfig)) error {
return err
}
defer conn.Close(context.Background())
return start.InitSchema14(ctx, conn)
if err := start.InitSchema14(ctx, conn); err != nil {
return err
}
return start.ApplyApiPrivileges(ctx, conn)
}

// Recreate postgres database by connecting to template1
Expand Down
38 changes: 38 additions & 0 deletions apps/cli-go/internal/db/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ func SetupDatabase(ctx context.Context, conn *pgx.Conn, host string, w io.Writer
if err := initSchema(ctx, conn, host, w); err != nil {
return err
}
if err := ApplyApiPrivileges(ctx, conn); err != nil {
return err
}
// Create vault secrets first so roles.sql can reference them
if err := vault.UpsertVaultSecrets(ctx, utils.Config.Db.Vault, conn); err != nil {
return err
Expand All @@ -394,3 +397,38 @@ func SetupDatabase(ctx context.Context, conn *pgx.Conn, host string, w io.Writer
}
return err
}

// RevokeDefaultDataApiPrivilegesSql matches the SQL that Studio runs at cloud project creation
// when the "Default privileges for new entities" toggle is off. It removes the default GRANTs
// applied by the initial schema so newly-created entities in `public` owned by `postgres` are
// not exposed through the Data API roles until explicit GRANTs are issued.
const RevokeDefaultDataApiPrivilegesSql = `
alter default privileges for role postgres in schema public
revoke select, insert, update, delete on tables from anon, authenticated, service_role;
alter default privileges for role postgres in schema public
revoke usage, select on sequences from anon, authenticated, service_role;
alter default privileges for role postgres in schema public
revoke execute on functions from anon, authenticated, service_role;
`

// ApplyApiPrivileges adjusts the default privileges on the `public` schema to match the
// `[api].auto_expose_new_tables` flag in config.toml. The flag is tri-state to give users a
// safe migration window:
//
// - unset (default today): keep the bundled initial-schema GRANTs in place, so local matches
// long-standing behaviour. This implicit default flips to false on May 30, 2026, and the
// flag is removed entirely in October 2026 (always-revoked behaviour).
// - true: explicit opt-in to today's behaviour. Treated identically to unset for now; from
// May 30 the CLI will warn that the flag is being deprecated.
// - false: revoke the default Data API GRANTs so newly-created entities in `public` require
// explicit GRANTs to surface through the Data API, matching the new cloud default.
func ApplyApiPrivileges(ctx context.Context, conn *pgx.Conn) error {
if utils.Config.Api.AutoExposeNewTables == nil || *utils.Config.Api.AutoExposeNewTables {
return nil
}
file, err := migration.NewMigrationFromReader(strings.NewReader(RevokeDefaultDataApiPrivilegesSql))
if err != nil {
return err
}
return file.ExecBatch(ctx, conn)
}
37 changes: 37 additions & 0 deletions apps/cli-go/internal/db/start/start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,43 @@ func TestSetupDatabase(t *testing.T) {
assert.Empty(t, apitest.ListUnmatchedRequests())
})

t.Run("revokes default data api privileges when auto_expose_new_tables is false", func(t *testing.T) {
utils.Config.Db.MajorVersion = 14
flag := false
utils.Config.Api.AutoExposeNewTables = &flag
defer func() {
utils.Config.Db.MajorVersion = 15
utils.Config.Api.AutoExposeNewTables = nil
}()
utils.Config.Db.Port = 5432
utils.GlobalsSql = "create schema public"
utils.InitialSchemaPg14Sql = "create schema private"
// Setup in-memory fs
fsys := afero.NewMemMapFs()
roles := "create role postgres"
require.NoError(t, afero.WriteFile(fsys, utils.CustomRolesPath, []byte(roles), 0644))
// Setup mock postgres: the revoke SQL must execute between the initial schema and roles.sql
conn := pgtest.NewConn()
defer conn.Close(t)
conn.Query(utils.GlobalsSql).
Reply("CREATE SCHEMA").
Query(utils.InitialSchemaPg14Sql).
Reply("CREATE SCHEMA").
Query("alter default privileges for role postgres in schema public\n revoke select, insert, update, delete on tables from anon, authenticated, service_role").
Reply("ALTER DEFAULT PRIVILEGES").
Query("alter default privileges for role postgres in schema public\n revoke usage, select on sequences from anon, authenticated, service_role").
Reply("ALTER DEFAULT PRIVILEGES").
Query("alter default privileges for role postgres in schema public\n revoke execute on functions from anon, authenticated, service_role").
Reply("ALTER DEFAULT PRIVILEGES").
Query(roles).
Reply("CREATE ROLE")
// Run test
err := SetupLocalDatabase(context.Background(), "", fsys, io.Discard, conn.Intercept)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
})

t.Run("throws error on connect failure", func(t *testing.T) {
utils.Config.Db.Port = 0
// Run test
Expand Down
9 changes: 9 additions & 0 deletions apps/cli-go/pkg/config/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ type (
Schemas []string `toml:"schemas" json:"schemas"`
ExtraSearchPath []string `toml:"extra_search_path" json:"extra_search_path"`
MaxRows uint `toml:"max_rows" json:"max_rows"`
// When unset (default today), new tables, views, sequences and functions created in
// the `public` schema by `postgres` are automatically reachable through the Data API
// roles `anon`, `authenticated`, and `service_role`, matching long-standing local
// behaviour. Set to false to match the new cloud default and require explicit GRANTs
// to expose entities through the Data API; set to true to opt out of the upcoming
// transition once the platform default flips. Stored as a pointer so the migration
// path (unset -> default true today, default false from May 30, removed in October)
// can flip the implicit value without losing the explicit user choice.
AutoExposeNewTables *bool `toml:"auto_expose_new_tables,omitempty" json:"auto_expose_new_tables,omitempty"`
// Local only config
Image string `toml:"-" json:"-"`
KongImage string `toml:"-" json:"-"`
Expand Down
7 changes: 7 additions & 0 deletions apps/cli-go/pkg/config/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,10 @@ func TestApiDiff(t *testing.T) {
assertSnapshotEqual(t, diff)
})
}

func TestApiAutoExposeNewTablesDefault(t *testing.T) {
t.Run("is unset on a fresh config so today's implicit-true behaviour applies", func(t *testing.T) {
cfg := NewConfig()
assert.Nil(t, cfg.Api.AutoExposeNewTables)
})
}
6 changes: 6 additions & 0 deletions apps/cli-go/pkg/config/templates/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ extra_search_path = ["public", "extensions"]
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
# for accidental or malicious requests.
max_rows = 1000
# Controls whether new tables, views, sequences and functions created in the `public` schema by
# `postgres` are reachable through the Data API roles (`anon`, `authenticated`, `service_role`)
# without explicit GRANTs. Leave unset today to preserve local behaviour. The implicit default
# flips to `false` on 2026-05-30 to match the new cloud default, and the field is removed in
# 2026-10-30 once the always-revoked behaviour is permanent. Set to `false` to opt in early.
# auto_expose_new_tables = false

[api.tls]
# Enable HTTPS endpoints locally using a self-signed certificate.
Expand Down
Loading
Loading