fix(api): reduce event spam with block list
This commit is contained in:
120
packages/validation/src/event-blocklist.ts
Normal file
120
packages/validation/src/event-blocklist.ts
Normal 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;
|
||||
}
|
||||
@@ -593,3 +593,4 @@ export type ICreateImport = z.infer<typeof zCreateImport>;
|
||||
|
||||
export * from './types.insights';
|
||||
export * from './track.validation';
|
||||
export * from './event-blocklist';
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user