Files
supabase/apps/studio/components/interfaces/Reports/Reports.constants.test.ts
Charis 9bdb757b6a feat(logs): brand Observability/EdgeFunctions SQL with SafeLogSqlFragment (#8) (#46466)
## 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 -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](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 -->
2026-05-28 10:30:57 -04:00

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'")
})
})