From 9534e00e950f99b1d2ff71162b03c0e37dcd10af Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 8 Jul 2025 00:47:44 -0400 Subject: [PATCH] Validate table prefix --- apps/web/src/actions/analytics/events.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/apps/web/src/actions/analytics/events.ts b/apps/web/src/actions/analytics/events.ts index e961cd2735..bfe44141ca 100644 --- a/apps/web/src/actions/analytics/events.ts +++ b/apps/web/src/actions/analytics/events.ts @@ -65,8 +65,19 @@ export const captureEvent = async ({ event, ...rest }: AnalyticsEvent) => { }; /** - * Helper function to build access control filters for analytics queries - * Includes both organization-level and user-level filtering + * SECURITY: Standardized access control filter builder + * + * This function ensures consistent access control across all analytics queries. + * It implements the principle of least privilege: + * - Personal accounts: Can only see their own data + * - Organization members: Can only see their own data within their org + * - Organization admins: Can see all data within their org OR their own data + * + * @param userId - Authenticated user ID (from authorizeAnalytics) + * @param isAdmin - Whether the user has admin privileges in their org + * @param orgId - Organization ID being queried (validated by authorizeAnalytics) + * @param tablePrefix - Optional table prefix for SQL queries (validated for safety) + * @returns SQL filter conditions and parameters for ClickHouse queries */ type AccessControlResult = { accessFilter: string; @@ -79,6 +90,13 @@ const buildAccessControlFilter = ( orgId: string | null | undefined, tablePrefix: string = '', ): AccessControlResult => { + // Security: Validate table prefix to prevent SQL injection + if (tablePrefix && !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tablePrefix)) { + throw new Error( + 'Invalid table prefix: must be alphanumeric with underscores', + ); + } + // For personal accounts, query by userId instead of orgId if (!orgId && !userId) { throw new Error('Personal accounts require authenticated user ID');