fix(api): reduce event spam with block list

This commit is contained in:
Carl-Gerhard Lindesvärd
2026-02-05 21:23:48 +00:00
parent 02897e11cb
commit ef8c2a4eee
3 changed files with 127 additions and 0 deletions

View File

@@ -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 ===
'<!doctype',
'<!entity',
'<xi:include',
'xsi:schemalocation',
// === SMTP Header Injection ===
'\r\n',
'bcc:',
// === Common File Scanning (paths as events) ===
'phpinfo.php',
'wp-config.php',
'.git/config',
'.env.backup',
'.env.bak',
'/vendor/phpunit/',
// === Malware/Botnet Indicators ===
'mozi.m',
'/setup.cgi?',
'/cgi-bin/',
// === Ruby Object Inspection Leaks ===
'#<article:0x',
'#<video:0x',
'#<brand:0x',
// === SQL Injection Patterns ===
'exec master.dbo',
'declare @',
"' and '",
"' or '",
"') or ",
"')and ",
];
// Patterns that indicate the "event" is actually a URL path being scanned
const PATH_SCAN_PATTERNS = [
/^\/[a-z_-]+\.(php|env|yml|yaml|json|xml|config|ini|bak|sql|log)/i,
/^\/\.[a-z]/i, // Hidden files like /.env, /.git
/^\/(wp-|wordpress)/i, // WordPress scanning
/^\/phpmyadmin/i,
/^\/.+\.php$/i, // Any .php path
];
/**
* Check if an event name should be blocked
* @param name - The event name to check
* @returns true if the event name should be blocked, false otherwise
*/
export function isBlockedEventName(name: string): boolean {
// Length check - attack payloads are often very long
if (name.length > 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;
}

View File

@@ -593,3 +593,4 @@ export type ICreateImport = z.infer<typeof zCreateImport>;
export * from './types.insights';
export * from './track.validation';
export * from './event-blocklist';

View File

@@ -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;