Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
56b598b
fix(cli): support older x64 CPUs (#5339)
jgoux May 22, 2026
627e534
feat(cli): port backups list and restore to native TypeScript (#5331)
Coly010 May 22, 2026
c16564a
feat(cli): port ssl-enforcement to native TypeScript (#5340)
Coly010 May 22, 2026
d9f31cc
fix(config): interpolate env() refs before schema decode (#5341)
Coly010 May 22, 2026
bbc5523
feat(config,stack): add auto_expose_new_tables configuration option (…
avallete May 26, 2026
0d53df0
fix(cli): support legacy migrations alias (#5355)
jgoux May 26, 2026
524ea4e
chore: adds additional sensitive keys (#5356)
staaldraad May 26, 2026
0cfdb72
feat(cli): port secrets commands to native TypeScript (#5357)
Coly010 May 26, 2026
6928009
feat(cli): align legacy telemetry payload with Go CLI (#5359)
Coly010 May 26, 2026
b5d1500
test(cli): stabilize functions dev watcher test (#5358)
jgoux May 26, 2026
5362083
fix(docker): restore Dependabot image updates (#5360)
jgoux May 26, 2026
f78caad
chore(deps): bump the npm-major group across 1 directory with 24 upda…
dependabot[bot] May 26, 2026
af9cbf1
fix(docker): repair mirror image workflow dispatch (#5363)
jgoux May 26, 2026
7a2f6d6
feat(cli): add support for vector buckets in CLI operations (#5230)
avallete May 27, 2026
8bc660e
fix(cli): restore completion subcommand tree in legacy shell (#5368)
Coly010 May 27, 2026
a47344e
fix(docker): bump the docker-minor group across 1 directory with 9 up…
dependabot[bot] May 27, 2026
bc3c9c7
fix(docker): bump supabase/postgres from 17.6.1.106 to 17.6.1.130 in …
dependabot[bot] May 27, 2026
3794837
chore(deps): bump fumadocs-mdx from 15.0.8 to 15.0.9 in the npm-major…
dependabot[bot] May 27, 2026
c87d52b
feat(cli): port network-restrictions commands to native TypeScript (#…
Coly010 May 27, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"config": {
"dbAllowedCidrs": ["0.0.0.0/0"],
"dbAllowedCidrsV6": ["::/0"]
}
},
"status": "applied"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"config": {
"dbAllowedCidrs": ["0.0.0.0/0"],
"dbAllowedCidrsV6": ["::/0"]
}
},
"status": "applied"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"config": {
"dbAllowedCidrs": ["0.0.0.0/0"],
"dbAllowedCidrsV6": ["::/0"]
}
},
"status": "applied"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"config": {
"dbAllowedCidrs": ["0.0.0.0/0"],
"dbAllowedCidrsV6": ["::/0"]
}
},
"status": "applied"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"config": {
"dbAllowedCidrs": ["0.0.0.0/0"],
"dbAllowedCidrsV6": ["::/0"]
}
},
"status": "applied"
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion apps/cli-e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@
}
},
"implicitDependencies": [
"cli-go"
"cli-go",
"supabase"
]
}
}
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
6 changes: 4 additions & 2 deletions apps/cli-go/internal/seed/buckets/buckets.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import (
)

func Run(ctx context.Context, projectRef string, interactive bool, fsys afero.Fs) error {
if len(projectRef) == 0 && len(utils.Config.Storage.Buckets) == 0 {
if len(projectRef) == 0 &&
len(utils.Config.Storage.Buckets) == 0 &&
!utils.Config.Storage.VectorBuckets.Enabled {
return nil
}
api, err := client.NewStorageAPI(ctx, projectRef)
Expand Down Expand Up @@ -47,7 +49,7 @@ func Run(ctx context.Context, projectRef string, interactive bool, fsys afero.Fs
return err
}
}
if utils.Config.Storage.VectorBuckets.Enabled && len(projectRef) > 0 {
if utils.Config.Storage.VectorBuckets.Enabled {
fmt.Fprintln(os.Stderr, "Updating vector buckets...")
if err := api.UpsertVectorBuckets(ctx, utils.Config.Storage.VectorBuckets.Buckets, prune); err != nil {
return err
Expand Down
47 changes: 47 additions & 0 deletions apps/cli-go/internal/seed/buckets/buckets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,51 @@ public = false`
assert.Len(t, gock.Pending(), 1)
assert.Empty(t, apitest.ListUnmatchedRequests())
})

t.Run("seeds vector buckets locally", func(t *testing.T) {
t.Cleanup(func() {
utils.Config.Storage.VectorBuckets.Enabled = false
clear(utils.Config.Storage.VectorBuckets.Buckets)
gock.OffAll()
})
utils.Config.Storage.VectorBuckets.Enabled = true
utils.Config.Storage.VectorBuckets.Buckets = map[string]struct{}{
"documents-openai": {},
"existing-vec": {},
}
// Setup mock api: regular buckets list is empty, vector list has one
// configured bucket plus one stale bucket that should be left alone
// because non-interactive prune defaults to false.
gock.New(utils.Config.Api.ExternalUrl).
Get("/storage/v1/bucket").
Reply(http.StatusOK).
JSON([]storage.BucketResponse{})
gock.New(utils.Config.Api.ExternalUrl).
Post("/storage/v1/vector/ListVectorBuckets").
Reply(http.StatusOK).
JSON(storage.ListVectorBucketsResponse{
VectorBuckets: []storage.VectorBucket{
{VectorBucketName: "existing-vec"},
{VectorBucketName: "stale-vec"},
},
})
gock.New(utils.Config.Api.ExternalUrl).
Post("/storage/v1/vector/CreateVectorBucket").
Reply(http.StatusOK).
JSON(map[string]string{})
gock.New(utils.Config.Api.ExternalUrl).
Post("/storage/v1/vector/DeleteVectorBucket").
Reply(http.StatusOK).
JSON(map[string]string{})
// Run test
err := Run(context.Background(), "", false, afero.NewMemMapFs())
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
// The DeleteVectorBucket mock should remain pending because non-interactive
// prune returns the default (false) and skips the delete.
pending := gock.Pending()
require.Len(t, pending, 1)
assert.Contains(t, pending[0].Request().URLStruct.Path, "DeleteVectorBucket")
})
}
91 changes: 66 additions & 25 deletions apps/cli-go/internal/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ func run(ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConf
isImgProxyEnabled := utils.Config.Storage.ImageTransformation != nil &&
utils.Config.Storage.ImageTransformation.Enabled && !isContainerExcluded(utils.Config.Storage.ImgProxyImage, excluded)
isS3ProtocolEnabled := utils.Config.Storage.S3Protocol != nil && utils.Config.Storage.S3Protocol.Enabled
isVectorBucketsEnabled := utils.Config.Storage.VectorBuckets.Enabled
fmt.Fprintln(os.Stderr, "Starting containers...")

workdir, err := os.Getwd()
Expand Down Expand Up @@ -938,35 +939,39 @@ EOF
// Start Storage.
if isStorageEnabled {
dockerStoragePath := "/mnt"
storageEnv := []string{
"DB_MIGRATIONS_FREEZE_AT=" + utils.Config.Storage.TargetMigration,
"ANON_KEY=" + utils.Config.Auth.AnonKey.Value,
"SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value,
"AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
fmt.Sprintf("JWT_JWKS=%s", jwks),
fmt.Sprintf("DATABASE_URL=postgresql://supabase_storage_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
fmt.Sprintf("FILE_SIZE_LIMIT=%v", utils.Config.Storage.FileSizeLimit),
"STORAGE_BACKEND=file",
"FILE_STORAGE_BACKEND_PATH=" + dockerStoragePath,
"TENANT_ID=stub",
// TODO: https://github.com/supabase/storage-api/issues/55
"STORAGE_S3_REGION=" + utils.Config.Storage.S3Credentials.Region,
"GLOBAL_S3_BUCKET=stub",
fmt.Sprintf("ENABLE_IMAGE_TRANSFORMATION=%t", isImgProxyEnabled),
fmt.Sprintf("IMGPROXY_URL=http://%s:5001", utils.ImgProxyId),
"TUS_URL_PATH=/storage/v1/upload/resumable",
fmt.Sprintf("S3_PROTOCOL_ENABLED=%t", isS3ProtocolEnabled),
"S3_PROTOCOL_ACCESS_KEY_ID=" + utils.Config.Storage.S3Credentials.AccessKeyId,
"S3_PROTOCOL_ACCESS_KEY_SECRET=" + utils.Config.Storage.S3Credentials.SecretAccessKey,
"S3_PROTOCOL_PREFIX=/storage/v1",
"UPLOAD_FILE_SIZE_LIMIT=52428800000",
"UPLOAD_FILE_SIZE_LIMIT_STANDARD=5242880000",
"SIGNED_UPLOAD_URL_EXPIRATION_TIME=7200",
}
if isVectorBucketsEnabled {
storageEnv = appendStorageVectorEnv(storageEnv, dbConfig)
}
if _, err := utils.DockerStart(
ctx,
container.Config{
Image: utils.Config.Storage.Image,
Env: []string{
"DB_MIGRATIONS_FREEZE_AT=" + utils.Config.Storage.TargetMigration,
"ANON_KEY=" + utils.Config.Auth.AnonKey.Value,
"SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value,
"AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
fmt.Sprintf("JWT_JWKS=%s", jwks),
fmt.Sprintf("DATABASE_URL=postgresql://supabase_storage_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
fmt.Sprintf("FILE_SIZE_LIMIT=%v", utils.Config.Storage.FileSizeLimit),
"STORAGE_BACKEND=file",
"FILE_STORAGE_BACKEND_PATH=" + dockerStoragePath,
"TENANT_ID=stub",
// TODO: https://github.com/supabase/storage-api/issues/55
"STORAGE_S3_REGION=" + utils.Config.Storage.S3Credentials.Region,
"GLOBAL_S3_BUCKET=stub",
fmt.Sprintf("ENABLE_IMAGE_TRANSFORMATION=%t", isImgProxyEnabled),
fmt.Sprintf("IMGPROXY_URL=http://%s:5001", utils.ImgProxyId),
"TUS_URL_PATH=/storage/v1/upload/resumable",
fmt.Sprintf("S3_PROTOCOL_ENABLED=%t", isS3ProtocolEnabled),
"S3_PROTOCOL_ACCESS_KEY_ID=" + utils.Config.Storage.S3Credentials.AccessKeyId,
"S3_PROTOCOL_ACCESS_KEY_SECRET=" + utils.Config.Storage.S3Credentials.SecretAccessKey,
"S3_PROTOCOL_PREFIX=/storage/v1",
"UPLOAD_FILE_SIZE_LIMIT=52428800000",
"UPLOAD_FILE_SIZE_LIMIT_STANDARD=5242880000",
"SIGNED_UPLOAD_URL_EXPIRATION_TIME=7200",
},
Env: storageEnv,
Healthcheck: &container.HealthConfig{
// For some reason, localhost resolves to IPv6 address on GitPod which breaks healthcheck.
Test: []string{
Expand Down Expand Up @@ -1371,6 +1376,42 @@ func appendGotrueExternalProviderEnv(env []string) []string {
return env
}

// appendStorageVectorEnv wires the storage container with the vector-bucket
// env contract from supabase/storage#1094. The CLI provides three CLI-owned
// defaults that the operator can override from their shell environment:
//
// - VECTOR_BUCKET_PROVIDER selects the local provider; pgvector is the only
// locally-available implementation.
// - VECTOR_STORE_MIGRATIONS_ENABLED tells storage to run its vector-store
// migrations on boot. Defaults on so a fresh stack is usable, but operators
// who run those migrations out of band can disable it.
// - VECTOR_DATABASE_URL hands storage a connection string with createdb
// permission so it can manage its own vectors database. We default to the
// postgres superuser on the local stack, mirroring the existing DATABASE_URL
// credentials, but operators are expected to override this to reach an
// external postgres in self-hosted setups.
func appendStorageVectorEnv(env []string, dbConfig pgconn.Config) []string {
envOrDefault := func(key, def string) string {
if v, ok := os.LookupEnv(key); ok {
return key + "=" + v
}
return key + "=" + def
}
defaultVectorURL := fmt.Sprintf(
"postgresql://postgres:%s@%s:%d/%s",
dbConfig.Password,
dbConfig.Host,
dbConfig.Port,
dbConfig.Database,
)
return append(env,
envOrDefault("VECTOR_ENABLED", "true"),
envOrDefault("VECTOR_BUCKET_PROVIDER", "pgvector"),
envOrDefault("VECTOR_STORE_MIGRATIONS_ENABLED", "true"),
envOrDefault("VECTOR_DATABASE_URL", defaultVectorURL),
)
}

func printSecurityNotice() {
fmt.Fprintln(os.Stderr, utils.Yellow("Local dev security notice"))
fmt.Fprintln(os.Stderr, "All services bind to 0.0.0.0 (network-accessible, not just localhost)")
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
Loading
Loading