From 84edf0dc94d36aaf6d75c85c8f3ff680c1fc3066 Mon Sep 17 00:00:00 2001
From: Vaibhav <117663341+7ttp@users.noreply.github.com>
Date: Thu, 18 Jun 2026 20:13:46 +0530
Subject: [PATCH] fix: protect csv import (#47040)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## TL;DR
fixes protected schema empty tables still exposing CSV import actions in
table editor...
## ref:
- closes https://github.com/supabase/supabase/issues/41358
- supersedes: https://github.com/supabase/supabase/pull/41362
## Summary by CodeRabbit
* **Bug Fixes**
* Updated the table empty state so CSV import actions are shown only
when the table is allowed to accept imports, and are hidden for
protected cases (including foreign-table scenarios).
* **Tests**
* Added an end-to-end test confirming that empty tables in protected
schemas do not expose the “Import data from CSV” button or the
drag-and-drop CSV hint.
---
.../components/grid/components/grid/Grid.tsx | 7 ++--
e2e/studio/features/table-editor.spec.ts | 34 +++++++++++++++++--
2 files changed, 36 insertions(+), 5 deletions(-)
diff --git a/apps/studio/components/grid/components/grid/Grid.tsx b/apps/studio/components/grid/components/grid/Grid.tsx
index 04cad32dd0b..d5ba2b094b7 100644
--- a/apps/studio/components/grid/components/grid/Grid.tsx
+++ b/apps/studio/components/grid/components/grid/Grid.tsx
@@ -89,11 +89,12 @@ export const Grid = memo(
const tableEntityType = snap.originalTable?.entity_type
const isForeignTable = tableEntityType === ENTITY_TYPE.FOREIGN_TABLE
const isTableEmpty = (rows ?? []).length === 0
+ const canImportData = snap.editable && !isForeignTable
const track = useTrack()
const { isDraggedOver, onDragOver, onFileDrop } = useCsvFileDrop({
- enabled: isTableEmpty && !isForeignTable,
+ enabled: isTableEmpty && canImportData,
onFileDropped: (file) => tableEditorSnap.onImportData(valtioRef(file)),
onTelemetryEvent: (eventName) => track(eventName),
})
@@ -322,7 +323,7 @@ export const Grid = memo(
started.
- ) : (
+ ) : canImportData ? (
- )}
+ ) : null}
) : (
diff --git a/e2e/studio/features/table-editor.spec.ts b/e2e/studio/features/table-editor.spec.ts
index e149f30fd3d..0d1688e34ca 100644
--- a/e2e/studio/features/table-editor.spec.ts
+++ b/e2e/studio/features/table-editor.spec.ts
@@ -1,7 +1,5 @@
import fs from 'fs'
import path from 'path'
-import { expect, Page } from '@playwright/test'
-
import { env } from '../env.config.js'
import { expectClipboardValue } from '../utils/clipboard.js'
import { dropTable, query } from '../utils/db/index.js'
@@ -16,6 +14,7 @@ import {
waitForGridDataToLoad,
waitForTableToLoad,
} from '../utils/wait-for-response.js'
+import { expect, Page } from '@playwright/test'
const deleteTable = async (page: Page, ref: string, tableName: string) => {
const viewLocator = page.getByLabel(`View ${tableName}`)
@@ -155,6 +154,37 @@ testRunner('table editor', () => {
await expect(page.getByLabel(`View ${authTableMfa}`)).toBeVisible()
})
+ test('protected schema empty tables do not expose CSV import actions', async ({ page, ref }) => {
+ const emptyAuthTables = await query<{ relname: string }>(`
+ select relname
+ from pg_stat_user_tables
+ where schemaname = 'auth'
+ and n_live_tup = 0
+ and relname <> 'schema_migrations'
+ order by relname
+ limit 1;
+ `)
+ test.skip(emptyAuthTables.length === 0, 'Requires an empty auth table in the test dataset')
+ const [{ relname: tableName }] = emptyAuthTables
+
+ await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
+
+ await page.getByTestId('schema-selector').click()
+ await page.getByPlaceholder('Find schema...').fill('auth')
+
+ const tableLoadPromise = waitForTableToLoad(page, ref, 'auth')
+ await page.getByRole('option', { name: 'auth' }).click()
+ await tableLoadPromise
+
+ await expect(page.getByRole('button', { name: `View ${tableName}`, exact: true })).toBeVisible()
+ await page.getByRole('button', { name: `View ${tableName}`, exact: true }).click()
+ await page.waitForURL(/\/editor\/\d+\?schema=auth$/)
+
+ await expect(page.getByText('This table is empty')).toBeVisible()
+ await expect(page.getByRole('button', { name: 'Import data from CSV' })).not.toBeVisible()
+ await expect(page.getByText('or drag and drop a CSV file here')).not.toBeVisible()
+ })
+
test('should show rls accordingly', async ({ page, ref }) => {
const tableNameRlsEnabled = 'pw_table_rls_enabled'
const tableNameRlsDisabled = 'pw_table_rls_disabled'