mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 19:35:06 +03:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Refactor / security hardening — continues the analytics SQL provenance-tracking series (PR 8). ## What is the current behavior? - `generateRegexpWhere` (unsafe: interpolates user-controlled filter keys/values without escaping) still exists alongside `generateRegexpWhereSafe` and its tests only cover the old function. - `usePostgrestOverviewMetrics` builds a SQL query string with plain string interpolation and calls the analytics endpoint directly via `get()`. - `edge-functions-last-hour-stats-query` builds a SQL query with `functionIds` escaped via Postgres-only `quoteLiteral` and calls the analytics endpoint directly via `post()`. - `executeAnalyticsSql` has no way to pass a `key` query-string param for network-tool identification. - `rawSql('minute')` / `rawSql('hour')` / `rawSql('day')` and `rawSql(value ? 'true' : 'false')` are used for static strings that could be expressed with the `safeSql` template tag. ## What is the new behavior? - `generateRegexpWhere` is deleted; its tests are replaced with `generateRegexpWhereSafe` coverage including injection-attempt cases (`level OR id IS NOT NULL`, `request.method); DROP TABLE edge_logs; --`) that verify predicates are silently dropped rather than emitted. - `usePostgrestOverviewMetrics` returns `SafeLogSqlFragment` from its SQL builder and routes through `executeAnalyticsSql`. - `edge-functions-last-hour-stats-query` uses `analyticsLiteral` (BigQuery/ClickHouse-correct escaping) instead of `quoteLiteral` (Postgres-only) and routes through `executeAnalyticsSql`. - `executeAnalyticsSql` accepts an optional `key?: string` forwarded as a query-string param on both GET and POST requests; `key: 'last-hour-stats'` is restored on the edge-functions query. - Static `rawSql('...')` calls replaced with `safeSql\`...\`` template literals throughout. ## Additional context <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Bug Fixes - Removed legacy unsafe SQL-filter utility from Reports ## Chores - Enhanced analytics SQL execution infrastructure with improved error handling - Added optional request identification parameter to analytics query execution - Refined SQL filtering mechanisms in reporting features <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46466?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
122 lines
3.4 KiB
TypeScript
122 lines
3.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { generateRegexpWhereSafe } from './Reports.constants'
|
|
import type { ReportFilterItem } from './Reports.types'
|
|
|
|
describe('generateRegexpWhereSafe', () => {
|
|
it('should return empty fragment when no filters provided', () => {
|
|
const result = generateRegexpWhereSafe([])
|
|
expect(result).toBe('')
|
|
})
|
|
|
|
it('should generate WHERE clause for single filter', () => {
|
|
const filters: ReportFilterItem[] = [
|
|
{
|
|
key: 'request.path',
|
|
value: '/api/users',
|
|
compare: 'is',
|
|
},
|
|
]
|
|
const result = generateRegexpWhereSafe(filters, true)
|
|
expect(result).toBe("WHERE `request`.`path` = '/api/users'")
|
|
})
|
|
|
|
it('should generate AND clause for single filter with prepend=false', () => {
|
|
const filters: ReportFilterItem[] = [
|
|
{
|
|
key: 'request.path',
|
|
value: '/api/users',
|
|
compare: 'is',
|
|
},
|
|
]
|
|
const result = generateRegexpWhereSafe(filters, false)
|
|
expect(result).toBe("AND `request`.`path` = '/api/users'")
|
|
})
|
|
|
|
it('should handle different comparison operators', () => {
|
|
const filters: ReportFilterItem[] = [
|
|
{
|
|
key: 'request.path',
|
|
value: '/api/*',
|
|
compare: 'matches',
|
|
},
|
|
{
|
|
key: 'response.status_code',
|
|
value: 404,
|
|
compare: 'is',
|
|
},
|
|
]
|
|
const result = generateRegexpWhereSafe(filters, true)
|
|
expect(result).toBe(
|
|
"WHERE REGEXP_CONTAINS(`request`.`path`, '/api/*') AND `response`.`status_code` = 404"
|
|
)
|
|
})
|
|
|
|
it('should handle numbers', () => {
|
|
const filters: ReportFilterItem[] = [
|
|
{
|
|
key: 'request.status_code',
|
|
value: 200,
|
|
compare: 'is',
|
|
},
|
|
]
|
|
const result = generateRegexpWhereSafe(filters, true)
|
|
expect(result).toBe('WHERE `request`.`status_code` = 200')
|
|
})
|
|
|
|
it('should escape single quotes in string values', () => {
|
|
const filters: ReportFilterItem[] = [
|
|
{
|
|
key: 'request.path',
|
|
value: "/it's/here",
|
|
compare: 'is',
|
|
},
|
|
]
|
|
const result = generateRegexpWhereSafe(filters, true)
|
|
expect(result).toBe("WHERE `request`.`path` = '/it''s/here'")
|
|
})
|
|
|
|
it('should drop filters with injection-attempt keys (OR injection)', () => {
|
|
const filters: ReportFilterItem[] = [
|
|
{
|
|
key: 'level OR id IS NOT NULL',
|
|
value: 'info',
|
|
compare: 'is',
|
|
},
|
|
]
|
|
// Key contains spaces — quotedIdent rejects it, predicate is dropped entirely
|
|
const result = generateRegexpWhereSafe(filters, true)
|
|
expect(result).toBe('')
|
|
})
|
|
|
|
it('should drop filters with injection-attempt keys (semicolon injection)', () => {
|
|
const filters: ReportFilterItem[] = [
|
|
{
|
|
key: 'request.method); DROP TABLE edge_logs; --',
|
|
value: 'GET',
|
|
compare: 'is',
|
|
},
|
|
]
|
|
// Key fails ident validation — predicate dropped entirely, no SQL emitted
|
|
const result = generateRegexpWhereSafe(filters, true)
|
|
expect(result).toBe('')
|
|
})
|
|
|
|
it('should drop invalid keys but keep valid ones', () => {
|
|
const filters: ReportFilterItem[] = [
|
|
{
|
|
key: 'bad key!',
|
|
value: 'anything',
|
|
compare: 'is',
|
|
},
|
|
{
|
|
key: 'request.method',
|
|
value: 'GET',
|
|
compare: 'is',
|
|
},
|
|
]
|
|
const result = generateRegexpWhereSafe(filters, true)
|
|
expect(result).toBe("WHERE `request`.`method` = 'get'")
|
|
})
|
|
})
|