diff --git a/packages/validation/src/event-blocklist.ts b/packages/validation/src/event-blocklist.ts new file mode 100644 index 00000000..e9554065 --- /dev/null +++ b/packages/validation/src/event-blocklist.ts @@ -0,0 +1,120 @@ +const MAX_EVENT_LENGTH = 80; + +// Substrings that indicate attack/spam payloads +const BLOCKED_SUBSTRINGS = [ + // === Security Scanner Domains === + 'oastify.com', + 'burpcollaborator', + 'interact.sh', + 'oast.me', + + // === SQL Injection === + 'pg_sleep', + 'waitfor delay', + 'xp_dirtree', + 'load_file(', + 'extractvalue(', + 'dbms_pipe.receive_message', + 'union select', + + // === Command Injection === + 'nslookup ', + '/bin/sleep', + '/bin/bash', + 'cmd.exe', + 'wget+http', + 'wget http', + 'chmod+777', + 'chmod 777', + + // === Java/Code Execution === + 'processbuilder', + 'runtime.getruntime', + 'java.lang.processbuilder', + 'eval-stdin.php', + + // === Path Traversal === + '../', + '..\\', + '%2e%2e', + '%u002e%u002e', + '/etc/passwd', + '/etc/shadow', + 'win.ini', + 'system.ini', + + // === Template/SSTI Injection === + '${', + '%{', + + // === XXE / XML Attacks === + ' MAX_EVENT_LENGTH) return true; + + // Contains newlines (always suspicious for event names) + if (name.includes('\n') || name.includes('\r')) return true; + + // Substring blocklist (case-insensitive) + const lower = name.toLowerCase(); + if (BLOCKED_SUBSTRINGS.some((blocked) => lower.includes(blocked))) { + return true; + } + + // Path scanning patterns + if (PATH_SCAN_PATTERNS.some((pattern) => pattern.test(name))) { + return true; + } + + return false; +} diff --git a/packages/validation/src/index.ts b/packages/validation/src/index.ts index 5ef2ef9b..ff5ff260 100644 --- a/packages/validation/src/index.ts +++ b/packages/validation/src/index.ts @@ -593,3 +593,4 @@ export type ICreateImport = z.infer; export * from './types.insights'; export * from './track.validation'; +export * from './event-blocklist'; diff --git a/packages/validation/src/track.validation.ts b/packages/validation/src/track.validation.ts index 18520f52..4ae685fb 100644 --- a/packages/validation/src/track.validation.ts +++ b/packages/validation/src/track.validation.ts @@ -2,6 +2,8 @@ import { z } from 'zod'; import { RESERVED_EVENT_NAMES } from '@openpanel/constants'; +import { isBlockedEventName } from './event-blocklist'; + export const zTrackPayload = z .object({ name: z.string().min(1), @@ -12,6 +14,10 @@ export const zTrackPayload = z message: `Event name cannot be one of the reserved names: ${RESERVED_EVENT_NAMES.join(', ')}`, path: ['name'], }) + .refine((data) => !isBlockedEventName(data.name), { + message: 'Event name contains blocked content', + path: ['name'], + }) .refine( (data) => { if (data.name !== 'revenue') return true;