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 './types.insights';
|
||||||
export * from './track.validation';
|
export * from './track.validation';
|
||||||
|
export * from './event-blocklist';
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
import { RESERVED_EVENT_NAMES } from '@openpanel/constants';
|
import { RESERVED_EVENT_NAMES } from '@openpanel/constants';
|
||||||
|
|
||||||
|
import { isBlockedEventName } from './event-blocklist';
|
||||||
|
|
||||||
export const zTrackPayload = z
|
export const zTrackPayload = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().min(1),
|
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(', ')}`,
|
message: `Event name cannot be one of the reserved names: ${RESERVED_EVENT_NAMES.join(', ')}`,
|
||||||
path: ['name'],
|
path: ['name'],
|
||||||
})
|
})
|
||||||
|
.refine((data) => !isBlockedEventName(data.name), {
|
||||||
|
message: 'Event name contains blocked content',
|
||||||
|
path: ['name'],
|
||||||
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
if (data.name !== 'revenue') return true;
|
if (data.name !== 'revenue') return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user