observability-toolkit 1.6.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -91
- package/dist/backends/index.d.ts +146 -0
- package/dist/backends/index.d.ts.map +1 -1
- package/dist/backends/index.js +65 -1
- package/dist/backends/index.js.map +1 -1
- package/dist/backends/local-jsonl-boolean-search.test.js +1 -23
- package/dist/backends/local-jsonl-boolean-search.test.js.map +1 -1
- package/dist/backends/local-jsonl.d.ts +4 -1
- package/dist/backends/local-jsonl.d.ts.map +1 -1
- package/dist/backends/local-jsonl.js +216 -6
- package/dist/backends/local-jsonl.js.map +1 -1
- package/dist/backends/local-jsonl.test.js +715 -26
- package/dist/backends/local-jsonl.test.js.map +1 -1
- package/dist/backends/signoz-api.d.ts +32 -0
- package/dist/backends/signoz-api.d.ts.map +1 -1
- package/dist/backends/signoz-api.js +237 -33
- package/dist/backends/signoz-api.js.map +1 -1
- package/dist/backends/signoz-api.test.js +410 -63
- package/dist/backends/signoz-api.test.js.map +1 -1
- package/dist/lib/constants.d.ts +16 -0
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +121 -5
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/constants.test.js +202 -15
- package/dist/lib/constants.test.js.map +1 -1
- package/dist/lib/error-sanitizer.d.ts +57 -0
- package/dist/lib/error-sanitizer.d.ts.map +1 -0
- package/dist/lib/error-sanitizer.js +197 -0
- package/dist/lib/error-sanitizer.js.map +1 -0
- package/dist/lib/error-sanitizer.test.d.ts +8 -0
- package/dist/lib/error-sanitizer.test.d.ts.map +1 -0
- package/dist/lib/error-sanitizer.test.js +342 -0
- package/dist/lib/error-sanitizer.test.js.map +1 -0
- package/dist/lib/file-utils.d.ts +210 -0
- package/dist/lib/file-utils.d.ts.map +1 -1
- package/dist/lib/file-utils.js +529 -14
- package/dist/lib/file-utils.js.map +1 -1
- package/dist/lib/file-utils.test.js +657 -3
- package/dist/lib/file-utils.test.js.map +1 -1
- package/dist/lib/indexer.d.ts +19 -1
- package/dist/lib/indexer.d.ts.map +1 -1
- package/dist/lib/indexer.js +84 -8
- package/dist/lib/indexer.js.map +1 -1
- package/dist/lib/indexer.test.js +187 -16
- package/dist/lib/indexer.test.js.map +1 -1
- package/dist/lib/input-validator.d.ts +98 -0
- package/dist/lib/input-validator.d.ts.map +1 -0
- package/dist/lib/input-validator.js +245 -0
- package/dist/lib/input-validator.js.map +1 -0
- package/dist/lib/input-validator.test.d.ts +2 -0
- package/dist/lib/input-validator.test.d.ts.map +1 -0
- package/dist/lib/input-validator.test.js +287 -0
- package/dist/lib/input-validator.test.js.map +1 -0
- package/dist/lib/query-sanitizer.d.ts +95 -0
- package/dist/lib/query-sanitizer.d.ts.map +1 -0
- package/dist/lib/query-sanitizer.js +187 -0
- package/dist/lib/query-sanitizer.js.map +1 -0
- package/dist/lib/query-sanitizer.test.d.ts +5 -0
- package/dist/lib/query-sanitizer.test.d.ts.map +1 -0
- package/dist/lib/query-sanitizer.test.js +299 -0
- package/dist/lib/query-sanitizer.test.js.map +1 -0
- package/dist/server.d.ts +49 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +97 -13
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +202 -0
- package/dist/server.test.js.map +1 -1
- package/dist/test-helpers/file-utils.d.ts +26 -0
- package/dist/test-helpers/file-utils.d.ts.map +1 -0
- package/dist/test-helpers/file-utils.js +43 -0
- package/dist/test-helpers/file-utils.js.map +1 -0
- package/dist/test-helpers/mock-backends.d.ts +28 -0
- package/dist/test-helpers/mock-backends.d.ts.map +1 -0
- package/dist/test-helpers/mock-backends.js +31 -0
- package/dist/test-helpers/mock-backends.js.map +1 -0
- package/dist/tools/health-check.js +1 -1
- package/dist/tools/health-check.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/query-evaluations.d.ts +183 -0
- package/dist/tools/query-evaluations.d.ts.map +1 -0
- package/dist/tools/query-evaluations.js +351 -0
- package/dist/tools/query-evaluations.js.map +1 -0
- package/dist/tools/query-evaluations.test.d.ts +5 -0
- package/dist/tools/query-evaluations.test.d.ts.map +1 -0
- package/dist/tools/query-evaluations.test.js +743 -0
- package/dist/tools/query-evaluations.test.js.map +1 -0
- package/dist/tools/query-llm-events.d.ts +62 -11
- package/dist/tools/query-llm-events.d.ts.map +1 -1
- package/dist/tools/query-llm-events.js +97 -37
- package/dist/tools/query-llm-events.js.map +1 -1
- package/dist/tools/query-llm-events.test.js +253 -0
- package/dist/tools/query-llm-events.test.js.map +1 -1
- package/dist/tools/query-logs.d.ts +32 -18
- package/dist/tools/query-logs.d.ts.map +1 -1
- package/dist/tools/query-logs.js +77 -44
- package/dist/tools/query-logs.js.map +1 -1
- package/dist/tools/query-logs.test.js +226 -64
- package/dist/tools/query-logs.test.js.map +1 -1
- package/dist/tools/query-metrics.d.ts +24 -24
- package/dist/tools/query-metrics.d.ts.map +1 -1
- package/dist/tools/query-metrics.js +102 -54
- package/dist/tools/query-metrics.js.map +1 -1
- package/dist/tools/query-metrics.test.js +35 -36
- package/dist/tools/query-metrics.test.js.map +1 -1
- package/dist/tools/query-traces.d.ts +66 -22
- package/dist/tools/query-traces.d.ts.map +1 -1
- package/dist/tools/query-traces.js +86 -42
- package/dist/tools/query-traces.js.map +1 -1
- package/dist/tools/query-traces.test.js +458 -36
- package/dist/tools/query-traces.test.js.map +1 -1
- package/package.json +1 -3
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClickHouse-specific query escaping and input validation
|
|
3
|
+
*
|
|
4
|
+
* Provides security utilities for sanitizing user inputs before
|
|
5
|
+
* including them in ClickHouse queries via SigNoz API.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Dangerous SQL/query patterns that should never appear in user input
|
|
9
|
+
* Case-insensitive matching is used
|
|
10
|
+
*/
|
|
11
|
+
export declare const DANGEROUS_PATTERNS: readonly string[];
|
|
12
|
+
/**
|
|
13
|
+
* Escape a string value for use in ClickHouse single-quoted strings
|
|
14
|
+
*
|
|
15
|
+
* ClickHouse uses backslash escaping for special characters.
|
|
16
|
+
* @see https://clickhouse.com/docs/en/sql-reference/syntax#string
|
|
17
|
+
*
|
|
18
|
+
* @param value - The string value to escape
|
|
19
|
+
* @returns Escaped string safe for use in single-quoted ClickHouse strings
|
|
20
|
+
*/
|
|
21
|
+
export declare function escapeClickHouseString(value: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Escape LIKE wildcard characters for use in ClickHouse LIKE patterns
|
|
24
|
+
*
|
|
25
|
+
* This escapes % and _ which have special meaning in LIKE patterns.
|
|
26
|
+
* Use this when the user's input should be treated as a literal string
|
|
27
|
+
* in a LIKE clause, not as a pattern.
|
|
28
|
+
*
|
|
29
|
+
* @param value - The string value to escape for LIKE
|
|
30
|
+
* @returns Escaped string with LIKE wildcards escaped
|
|
31
|
+
*/
|
|
32
|
+
export declare function escapeClickHouseLike(value: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Sanitize an identifier (column name, table name, etc.)
|
|
35
|
+
*
|
|
36
|
+
* Only allows alphanumeric characters, underscores, and dots.
|
|
37
|
+
* Removes all other characters.
|
|
38
|
+
*
|
|
39
|
+
* @param name - The identifier to sanitize
|
|
40
|
+
* @returns Sanitized identifier containing only safe characters
|
|
41
|
+
*/
|
|
42
|
+
export declare function sanitizeIdentifier(name: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Maximum length for query input validation
|
|
45
|
+
* Prevents performance issues with very long inputs
|
|
46
|
+
*/
|
|
47
|
+
export declare const MAX_QUERY_INPUT_LENGTH = 10000;
|
|
48
|
+
/**
|
|
49
|
+
* Check if a string contains any dangerous SQL patterns
|
|
50
|
+
*
|
|
51
|
+
* Uses case-insensitive matching and word boundary detection
|
|
52
|
+
* for most patterns to reduce false positives.
|
|
53
|
+
*
|
|
54
|
+
* Defense in depth: Rejects inputs exceeding MAX_QUERY_INPUT_LENGTH
|
|
55
|
+
* to prevent performance issues with very long strings.
|
|
56
|
+
*
|
|
57
|
+
* @param input - The input string to check
|
|
58
|
+
* @returns true if dangerous pattern found, false otherwise
|
|
59
|
+
*/
|
|
60
|
+
export declare function containsDangerousPattern(input: string): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Validate that user input does not contain dangerous patterns
|
|
63
|
+
*
|
|
64
|
+
* Throws an error if dangerous patterns are detected, including
|
|
65
|
+
* the field name for better error messages.
|
|
66
|
+
*
|
|
67
|
+
* @param input - The input string to validate
|
|
68
|
+
* @param fieldName - Name of the field being validated (for error messages)
|
|
69
|
+
* @throws Error if dangerous pattern is found
|
|
70
|
+
*/
|
|
71
|
+
export declare function validateQueryInput(input: string, fieldName: string): void;
|
|
72
|
+
/**
|
|
73
|
+
* Escape and validate a string value for use in WHERE clause conditions
|
|
74
|
+
*
|
|
75
|
+
* Combines validation and escaping for convenience.
|
|
76
|
+
*
|
|
77
|
+
* @param value - The value to escape
|
|
78
|
+
* @param fieldName - Name of the field (for error messages)
|
|
79
|
+
* @returns Escaped string safe for ClickHouse queries
|
|
80
|
+
* @throws Error if dangerous pattern is found
|
|
81
|
+
*/
|
|
82
|
+
export declare function escapeFilterValueSafe(value: string, fieldName: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* Escape and validate a value for use in LIKE clauses
|
|
85
|
+
*
|
|
86
|
+
* Escapes both ClickHouse string escapes and LIKE wildcards,
|
|
87
|
+
* and validates for dangerous patterns.
|
|
88
|
+
*
|
|
89
|
+
* @param value - The value to escape
|
|
90
|
+
* @param fieldName - Name of the field (for error messages)
|
|
91
|
+
* @returns Escaped string safe for ClickHouse LIKE clauses
|
|
92
|
+
* @throws Error if dangerous pattern is found
|
|
93
|
+
*/
|
|
94
|
+
export declare function escapeLikeValueSafe(value: string, fieldName: string): string;
|
|
95
|
+
//# sourceMappingURL=query-sanitizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-sanitizer.d.ts","sourceRoot":"","sources":["../../src/lib/query-sanitizer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAuBtC,CAAC;AAEX;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAc5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,QAAQ,CAAC;AAU5C;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CA6B/D;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAIzE;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG9E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAK5E"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClickHouse-specific query escaping and input validation
|
|
3
|
+
*
|
|
4
|
+
* Provides security utilities for sanitizing user inputs before
|
|
5
|
+
* including them in ClickHouse queries via SigNoz API.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Dangerous SQL/query patterns that should never appear in user input
|
|
9
|
+
* Case-insensitive matching is used
|
|
10
|
+
*/
|
|
11
|
+
export const DANGEROUS_PATTERNS = [
|
|
12
|
+
'DROP',
|
|
13
|
+
'DELETE',
|
|
14
|
+
'INSERT',
|
|
15
|
+
'UPDATE',
|
|
16
|
+
'ALTER',
|
|
17
|
+
'CREATE',
|
|
18
|
+
'TRUNCATE',
|
|
19
|
+
'--',
|
|
20
|
+
'/*',
|
|
21
|
+
'*/',
|
|
22
|
+
';',
|
|
23
|
+
'UNION',
|
|
24
|
+
'INTO OUTFILE',
|
|
25
|
+
'INTO DUMPFILE',
|
|
26
|
+
'LOAD_FILE',
|
|
27
|
+
'SYSTEM',
|
|
28
|
+
'ATTACH',
|
|
29
|
+
'DETACH',
|
|
30
|
+
'RENAME',
|
|
31
|
+
'OPTIMIZE',
|
|
32
|
+
'GRANT',
|
|
33
|
+
'REVOKE',
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Escape a string value for use in ClickHouse single-quoted strings
|
|
37
|
+
*
|
|
38
|
+
* ClickHouse uses backslash escaping for special characters.
|
|
39
|
+
* @see https://clickhouse.com/docs/en/sql-reference/syntax#string
|
|
40
|
+
*
|
|
41
|
+
* @param value - The string value to escape
|
|
42
|
+
* @returns Escaped string safe for use in single-quoted ClickHouse strings
|
|
43
|
+
*/
|
|
44
|
+
export function escapeClickHouseString(value) {
|
|
45
|
+
return value
|
|
46
|
+
// Escape backslashes first (must be first to avoid double-escaping)
|
|
47
|
+
.replace(/\\/g, '\\\\')
|
|
48
|
+
// Escape single quotes
|
|
49
|
+
.replace(/'/g, "\\'")
|
|
50
|
+
// Escape null bytes
|
|
51
|
+
.replace(/\0/g, '\\0')
|
|
52
|
+
// Escape newlines
|
|
53
|
+
.replace(/\n/g, '\\n')
|
|
54
|
+
// Escape carriage returns
|
|
55
|
+
.replace(/\r/g, '\\r')
|
|
56
|
+
// Escape tabs
|
|
57
|
+
.replace(/\t/g, '\\t');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Escape LIKE wildcard characters for use in ClickHouse LIKE patterns
|
|
61
|
+
*
|
|
62
|
+
* This escapes % and _ which have special meaning in LIKE patterns.
|
|
63
|
+
* Use this when the user's input should be treated as a literal string
|
|
64
|
+
* in a LIKE clause, not as a pattern.
|
|
65
|
+
*
|
|
66
|
+
* @param value - The string value to escape for LIKE
|
|
67
|
+
* @returns Escaped string with LIKE wildcards escaped
|
|
68
|
+
*/
|
|
69
|
+
export function escapeClickHouseLike(value) {
|
|
70
|
+
// First escape backslashes (used as escape character in LIKE)
|
|
71
|
+
// Then escape the LIKE wildcards
|
|
72
|
+
return value
|
|
73
|
+
.replace(/\\/g, '\\\\')
|
|
74
|
+
.replace(/%/g, '\\%')
|
|
75
|
+
.replace(/_/g, '\\_');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Sanitize an identifier (column name, table name, etc.)
|
|
79
|
+
*
|
|
80
|
+
* Only allows alphanumeric characters, underscores, and dots.
|
|
81
|
+
* Removes all other characters.
|
|
82
|
+
*
|
|
83
|
+
* @param name - The identifier to sanitize
|
|
84
|
+
* @returns Sanitized identifier containing only safe characters
|
|
85
|
+
*/
|
|
86
|
+
export function sanitizeIdentifier(name) {
|
|
87
|
+
return name.replace(/[^a-zA-Z0-9_.]/g, '');
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Maximum length for query input validation
|
|
91
|
+
* Prevents performance issues with very long inputs
|
|
92
|
+
*/
|
|
93
|
+
export const MAX_QUERY_INPUT_LENGTH = 10000;
|
|
94
|
+
/**
|
|
95
|
+
* Escape regex metacharacters in a string for safe use in RegExp constructor
|
|
96
|
+
* Prevents ReDoS attacks when using user-influenced patterns
|
|
97
|
+
*/
|
|
98
|
+
function escapeRegexPattern(pattern) {
|
|
99
|
+
return pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Check if a string contains any dangerous SQL patterns
|
|
103
|
+
*
|
|
104
|
+
* Uses case-insensitive matching and word boundary detection
|
|
105
|
+
* for most patterns to reduce false positives.
|
|
106
|
+
*
|
|
107
|
+
* Defense in depth: Rejects inputs exceeding MAX_QUERY_INPUT_LENGTH
|
|
108
|
+
* to prevent performance issues with very long strings.
|
|
109
|
+
*
|
|
110
|
+
* @param input - The input string to check
|
|
111
|
+
* @returns true if dangerous pattern found, false otherwise
|
|
112
|
+
*/
|
|
113
|
+
export function containsDangerousPattern(input) {
|
|
114
|
+
// Defense in depth: reject excessively long inputs
|
|
115
|
+
// This prevents O(n) regex operations on very large strings
|
|
116
|
+
if (input.length > MAX_QUERY_INPUT_LENGTH) {
|
|
117
|
+
console.warn(`[SECURITY] Input length ${input.length} exceeds maximum ${MAX_QUERY_INPUT_LENGTH}`);
|
|
118
|
+
return true; // Treat as dangerous
|
|
119
|
+
}
|
|
120
|
+
const upperInput = input.toUpperCase();
|
|
121
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
122
|
+
// For comment markers and semicolons, do exact substring match
|
|
123
|
+
if (pattern === '--' || pattern === '/*' || pattern === '*/' || pattern === ';') {
|
|
124
|
+
if (input.includes(pattern)) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// For SQL keywords, check with word boundaries to reduce false positives
|
|
130
|
+
// e.g., "DROPPED" should not match "DROP", but "DROP TABLE" should
|
|
131
|
+
// Escape pattern to prevent ReDoS if pattern contains regex metacharacters
|
|
132
|
+
const escapedPattern = escapeRegexPattern(pattern);
|
|
133
|
+
const regex = new RegExp(`\\b${escapedPattern}\\b`, 'i');
|
|
134
|
+
if (regex.test(input)) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Validate that user input does not contain dangerous patterns
|
|
143
|
+
*
|
|
144
|
+
* Throws an error if dangerous patterns are detected, including
|
|
145
|
+
* the field name for better error messages.
|
|
146
|
+
*
|
|
147
|
+
* @param input - The input string to validate
|
|
148
|
+
* @param fieldName - Name of the field being validated (for error messages)
|
|
149
|
+
* @throws Error if dangerous pattern is found
|
|
150
|
+
*/
|
|
151
|
+
export function validateQueryInput(input, fieldName) {
|
|
152
|
+
if (containsDangerousPattern(input)) {
|
|
153
|
+
throw new Error(`Invalid ${fieldName}: contains potentially dangerous SQL pattern`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Escape and validate a string value for use in WHERE clause conditions
|
|
158
|
+
*
|
|
159
|
+
* Combines validation and escaping for convenience.
|
|
160
|
+
*
|
|
161
|
+
* @param value - The value to escape
|
|
162
|
+
* @param fieldName - Name of the field (for error messages)
|
|
163
|
+
* @returns Escaped string safe for ClickHouse queries
|
|
164
|
+
* @throws Error if dangerous pattern is found
|
|
165
|
+
*/
|
|
166
|
+
export function escapeFilterValueSafe(value, fieldName) {
|
|
167
|
+
validateQueryInput(value, fieldName);
|
|
168
|
+
return escapeClickHouseString(value);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Escape and validate a value for use in LIKE clauses
|
|
172
|
+
*
|
|
173
|
+
* Escapes both ClickHouse string escapes and LIKE wildcards,
|
|
174
|
+
* and validates for dangerous patterns.
|
|
175
|
+
*
|
|
176
|
+
* @param value - The value to escape
|
|
177
|
+
* @param fieldName - Name of the field (for error messages)
|
|
178
|
+
* @returns Escaped string safe for ClickHouse LIKE clauses
|
|
179
|
+
* @throws Error if dangerous pattern is found
|
|
180
|
+
*/
|
|
181
|
+
export function escapeLikeValueSafe(value, fieldName) {
|
|
182
|
+
validateQueryInput(value, fieldName);
|
|
183
|
+
// First escape LIKE wildcards, then escape for string context
|
|
184
|
+
const likeEscaped = escapeClickHouseLike(value);
|
|
185
|
+
return escapeClickHouseString(likeEscaped);
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=query-sanitizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-sanitizer.js","sourceRoot":"","sources":["../../src/lib/query-sanitizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-sanitizer.test.d.ts","sourceRoot":"","sources":["../../src/lib/query-sanitizer.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for query-sanitizer.ts
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it } from 'node:test';
|
|
5
|
+
import assert from 'node:assert';
|
|
6
|
+
import { escapeClickHouseString, escapeClickHouseLike, sanitizeIdentifier, containsDangerousPattern, validateQueryInput, escapeFilterValueSafe, escapeLikeValueSafe, DANGEROUS_PATTERNS, } from './query-sanitizer.js';
|
|
7
|
+
describe('query-sanitizer', () => {
|
|
8
|
+
describe('DANGEROUS_PATTERNS', () => {
|
|
9
|
+
it('should be a non-empty array', () => {
|
|
10
|
+
assert.ok(Array.isArray(DANGEROUS_PATTERNS));
|
|
11
|
+
assert.ok(DANGEROUS_PATTERNS.length > 0);
|
|
12
|
+
});
|
|
13
|
+
it('should include common SQL injection keywords', () => {
|
|
14
|
+
assert.ok(DANGEROUS_PATTERNS.includes('DROP'));
|
|
15
|
+
assert.ok(DANGEROUS_PATTERNS.includes('DELETE'));
|
|
16
|
+
assert.ok(DANGEROUS_PATTERNS.includes('INSERT'));
|
|
17
|
+
assert.ok(DANGEROUS_PATTERNS.includes('UPDATE'));
|
|
18
|
+
assert.ok(DANGEROUS_PATTERNS.includes('UNION'));
|
|
19
|
+
});
|
|
20
|
+
it('should include DDL keywords', () => {
|
|
21
|
+
assert.ok(DANGEROUS_PATTERNS.includes('ALTER'));
|
|
22
|
+
assert.ok(DANGEROUS_PATTERNS.includes('CREATE'));
|
|
23
|
+
assert.ok(DANGEROUS_PATTERNS.includes('TRUNCATE'));
|
|
24
|
+
});
|
|
25
|
+
it('should include comment markers', () => {
|
|
26
|
+
assert.ok(DANGEROUS_PATTERNS.includes('--'));
|
|
27
|
+
assert.ok(DANGEROUS_PATTERNS.includes('/*'));
|
|
28
|
+
assert.ok(DANGEROUS_PATTERNS.includes('*/'));
|
|
29
|
+
});
|
|
30
|
+
it('should include statement terminator', () => {
|
|
31
|
+
assert.ok(DANGEROUS_PATTERNS.includes(';'));
|
|
32
|
+
});
|
|
33
|
+
it('should include file operation keywords', () => {
|
|
34
|
+
assert.ok(DANGEROUS_PATTERNS.includes('INTO OUTFILE'));
|
|
35
|
+
assert.ok(DANGEROUS_PATTERNS.includes('INTO DUMPFILE'));
|
|
36
|
+
assert.ok(DANGEROUS_PATTERNS.includes('LOAD_FILE'));
|
|
37
|
+
});
|
|
38
|
+
it('should include ClickHouse-specific dangerous keywords', () => {
|
|
39
|
+
assert.ok(DANGEROUS_PATTERNS.includes('SYSTEM'));
|
|
40
|
+
assert.ok(DANGEROUS_PATTERNS.includes('ATTACH'));
|
|
41
|
+
assert.ok(DANGEROUS_PATTERNS.includes('DETACH'));
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe('escapeClickHouseString', () => {
|
|
45
|
+
it('should return empty string for empty input', () => {
|
|
46
|
+
assert.strictEqual(escapeClickHouseString(''), '');
|
|
47
|
+
});
|
|
48
|
+
it('should return unchanged string with no special characters', () => {
|
|
49
|
+
assert.strictEqual(escapeClickHouseString('hello'), 'hello');
|
|
50
|
+
assert.strictEqual(escapeClickHouseString('test123'), 'test123');
|
|
51
|
+
});
|
|
52
|
+
it('should escape single quotes', () => {
|
|
53
|
+
assert.strictEqual(escapeClickHouseString("it's"), "it\\'s");
|
|
54
|
+
assert.strictEqual(escapeClickHouseString("test'value"), "test\\'value");
|
|
55
|
+
assert.strictEqual(escapeClickHouseString("''"), "\\'\\'");
|
|
56
|
+
});
|
|
57
|
+
it('should escape backslashes', () => {
|
|
58
|
+
assert.strictEqual(escapeClickHouseString('path\\to\\file'), 'path\\\\to\\\\file');
|
|
59
|
+
assert.strictEqual(escapeClickHouseString('\\'), '\\\\');
|
|
60
|
+
});
|
|
61
|
+
it('should escape backslashes before single quotes', () => {
|
|
62
|
+
// Important: backslash followed by quote should become \\'
|
|
63
|
+
assert.strictEqual(escapeClickHouseString("\\'"), "\\\\\\'");
|
|
64
|
+
});
|
|
65
|
+
it('should escape null bytes', () => {
|
|
66
|
+
assert.strictEqual(escapeClickHouseString('test\0value'), 'test\\0value');
|
|
67
|
+
assert.strictEqual(escapeClickHouseString('\0'), '\\0');
|
|
68
|
+
});
|
|
69
|
+
it('should escape newlines', () => {
|
|
70
|
+
assert.strictEqual(escapeClickHouseString('line1\nline2'), 'line1\\nline2');
|
|
71
|
+
assert.strictEqual(escapeClickHouseString('\n'), '\\n');
|
|
72
|
+
});
|
|
73
|
+
it('should escape carriage returns', () => {
|
|
74
|
+
assert.strictEqual(escapeClickHouseString('line1\rline2'), 'line1\\rline2');
|
|
75
|
+
assert.strictEqual(escapeClickHouseString('\r\n'), '\\r\\n');
|
|
76
|
+
});
|
|
77
|
+
it('should escape tabs', () => {
|
|
78
|
+
assert.strictEqual(escapeClickHouseString('col1\tcol2'), 'col1\\tcol2');
|
|
79
|
+
assert.strictEqual(escapeClickHouseString('\t'), '\\t');
|
|
80
|
+
});
|
|
81
|
+
it('should handle multiple special characters', () => {
|
|
82
|
+
const input = "test'\n\\\t\0";
|
|
83
|
+
const expected = "test\\'\\n\\\\\\t\\0";
|
|
84
|
+
assert.strictEqual(escapeClickHouseString(input), expected);
|
|
85
|
+
});
|
|
86
|
+
it('should handle unicode characters unchanged', () => {
|
|
87
|
+
assert.strictEqual(escapeClickHouseString('unicode: \u00E9\u00F1'), 'unicode: \u00E9\u00F1');
|
|
88
|
+
assert.strictEqual(escapeClickHouseString('emoji: \uD83D\uDE00'), 'emoji: \uD83D\uDE00');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('escapeClickHouseLike', () => {
|
|
92
|
+
it('should return empty string for empty input', () => {
|
|
93
|
+
assert.strictEqual(escapeClickHouseLike(''), '');
|
|
94
|
+
});
|
|
95
|
+
it('should return unchanged string with no special characters', () => {
|
|
96
|
+
assert.strictEqual(escapeClickHouseLike('hello'), 'hello');
|
|
97
|
+
assert.strictEqual(escapeClickHouseLike('test123'), 'test123');
|
|
98
|
+
});
|
|
99
|
+
it('should escape percent sign', () => {
|
|
100
|
+
assert.strictEqual(escapeClickHouseLike('100%'), '100\\%');
|
|
101
|
+
assert.strictEqual(escapeClickHouseLike('%value%'), '\\%value\\%');
|
|
102
|
+
});
|
|
103
|
+
it('should escape underscore', () => {
|
|
104
|
+
assert.strictEqual(escapeClickHouseLike('test_value'), 'test\\_value');
|
|
105
|
+
assert.strictEqual(escapeClickHouseLike('_'), '\\_');
|
|
106
|
+
});
|
|
107
|
+
it('should escape backslashes', () => {
|
|
108
|
+
assert.strictEqual(escapeClickHouseLike('path\\file'), 'path\\\\file');
|
|
109
|
+
});
|
|
110
|
+
it('should escape backslash before wildcards', () => {
|
|
111
|
+
assert.strictEqual(escapeClickHouseLike('\\%'), '\\\\\\%');
|
|
112
|
+
assert.strictEqual(escapeClickHouseLike('\\_'), '\\\\\\_');
|
|
113
|
+
});
|
|
114
|
+
it('should handle multiple wildcards', () => {
|
|
115
|
+
assert.strictEqual(escapeClickHouseLike('%_%'), '\\%\\_\\%');
|
|
116
|
+
});
|
|
117
|
+
it('should not escape other special characters', () => {
|
|
118
|
+
// Single quotes, etc. are handled by escapeClickHouseString
|
|
119
|
+
assert.strictEqual(escapeClickHouseLike("it's"), "it's");
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe('sanitizeIdentifier', () => {
|
|
123
|
+
it('should return empty string for empty input', () => {
|
|
124
|
+
assert.strictEqual(sanitizeIdentifier(''), '');
|
|
125
|
+
});
|
|
126
|
+
it('should allow alphanumeric characters', () => {
|
|
127
|
+
assert.strictEqual(sanitizeIdentifier('abc123'), 'abc123');
|
|
128
|
+
assert.strictEqual(sanitizeIdentifier('ABC123'), 'ABC123');
|
|
129
|
+
});
|
|
130
|
+
it('should allow underscores', () => {
|
|
131
|
+
assert.strictEqual(sanitizeIdentifier('my_column'), 'my_column');
|
|
132
|
+
assert.strictEqual(sanitizeIdentifier('_private'), '_private');
|
|
133
|
+
});
|
|
134
|
+
it('should allow dots for qualified names', () => {
|
|
135
|
+
assert.strictEqual(sanitizeIdentifier('table.column'), 'table.column');
|
|
136
|
+
assert.strictEqual(sanitizeIdentifier('db.table.column'), 'db.table.column');
|
|
137
|
+
});
|
|
138
|
+
it('should remove spaces', () => {
|
|
139
|
+
assert.strictEqual(sanitizeIdentifier('my column'), 'mycolumn');
|
|
140
|
+
assert.strictEqual(sanitizeIdentifier(' column '), 'column');
|
|
141
|
+
});
|
|
142
|
+
it('should remove special characters', () => {
|
|
143
|
+
assert.strictEqual(sanitizeIdentifier('column;DROP'), 'columnDROP');
|
|
144
|
+
assert.strictEqual(sanitizeIdentifier('col--comment'), 'colcomment');
|
|
145
|
+
assert.strictEqual(sanitizeIdentifier("col'name"), 'colname');
|
|
146
|
+
});
|
|
147
|
+
it('should remove SQL injection attempts', () => {
|
|
148
|
+
assert.strictEqual(sanitizeIdentifier('column; DROP TABLE users'), 'columnDROPTABLEusers');
|
|
149
|
+
assert.strictEqual(sanitizeIdentifier('column/*comment*/'), 'columncomment');
|
|
150
|
+
});
|
|
151
|
+
it('should handle unicode by removing it', () => {
|
|
152
|
+
assert.strictEqual(sanitizeIdentifier('column\u00E9'), 'column');
|
|
153
|
+
assert.strictEqual(sanitizeIdentifier('\u4E2D\u6587'), '');
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
describe('containsDangerousPattern', () => {
|
|
157
|
+
it('should return false for empty string', () => {
|
|
158
|
+
assert.strictEqual(containsDangerousPattern(''), false);
|
|
159
|
+
});
|
|
160
|
+
it('should return false for normal strings', () => {
|
|
161
|
+
assert.strictEqual(containsDangerousPattern('hello'), false);
|
|
162
|
+
assert.strictEqual(containsDangerousPattern('test123'), false);
|
|
163
|
+
assert.strictEqual(containsDangerousPattern('my-service-name'), false);
|
|
164
|
+
});
|
|
165
|
+
it('should detect DROP keyword', () => {
|
|
166
|
+
assert.strictEqual(containsDangerousPattern('DROP TABLE'), true);
|
|
167
|
+
assert.strictEqual(containsDangerousPattern('drop table'), true);
|
|
168
|
+
assert.strictEqual(containsDangerousPattern('DrOp TaBlE'), true);
|
|
169
|
+
});
|
|
170
|
+
it('should detect DELETE keyword', () => {
|
|
171
|
+
assert.strictEqual(containsDangerousPattern('DELETE FROM'), true);
|
|
172
|
+
assert.strictEqual(containsDangerousPattern('delete from users'), true);
|
|
173
|
+
});
|
|
174
|
+
it('should detect INSERT keyword', () => {
|
|
175
|
+
assert.strictEqual(containsDangerousPattern('INSERT INTO'), true);
|
|
176
|
+
assert.strictEqual(containsDangerousPattern('insert values'), true);
|
|
177
|
+
});
|
|
178
|
+
it('should detect UPDATE keyword', () => {
|
|
179
|
+
assert.strictEqual(containsDangerousPattern('UPDATE users SET'), true);
|
|
180
|
+
assert.strictEqual(containsDangerousPattern('update table'), true);
|
|
181
|
+
});
|
|
182
|
+
it('should detect UNION keyword', () => {
|
|
183
|
+
assert.strictEqual(containsDangerousPattern('UNION SELECT'), true);
|
|
184
|
+
assert.strictEqual(containsDangerousPattern('union all'), true);
|
|
185
|
+
});
|
|
186
|
+
it('should detect comment markers', () => {
|
|
187
|
+
assert.strictEqual(containsDangerousPattern('value -- comment'), true);
|
|
188
|
+
assert.strictEqual(containsDangerousPattern('value /* comment */'), true);
|
|
189
|
+
assert.strictEqual(containsDangerousPattern('*/'), true);
|
|
190
|
+
});
|
|
191
|
+
it('should detect semicolon', () => {
|
|
192
|
+
assert.strictEqual(containsDangerousPattern('value; DROP'), true);
|
|
193
|
+
assert.strictEqual(containsDangerousPattern(';'), true);
|
|
194
|
+
});
|
|
195
|
+
it('should detect file operations', () => {
|
|
196
|
+
assert.strictEqual(containsDangerousPattern("INTO OUTFILE '/tmp/file'"), true);
|
|
197
|
+
assert.strictEqual(containsDangerousPattern('INTO DUMPFILE'), true);
|
|
198
|
+
assert.strictEqual(containsDangerousPattern('LOAD_FILE()'), true);
|
|
199
|
+
});
|
|
200
|
+
it('should detect ClickHouse system commands', () => {
|
|
201
|
+
assert.strictEqual(containsDangerousPattern('SYSTEM RELOAD'), true);
|
|
202
|
+
assert.strictEqual(containsDangerousPattern('ATTACH TABLE'), true);
|
|
203
|
+
assert.strictEqual(containsDangerousPattern('DETACH TABLE'), true);
|
|
204
|
+
});
|
|
205
|
+
it('should not flag words containing keywords', () => {
|
|
206
|
+
// "DROPPED" contains "DROP" but is not the DROP command
|
|
207
|
+
assert.strictEqual(containsDangerousPattern('DROPPED'), false);
|
|
208
|
+
assert.strictEqual(containsDangerousPattern('DELETED'), false);
|
|
209
|
+
assert.strictEqual(containsDangerousPattern('UPDATED'), false);
|
|
210
|
+
assert.strictEqual(containsDangerousPattern('INSERTED'), false);
|
|
211
|
+
});
|
|
212
|
+
it('should detect keywords at word boundaries', () => {
|
|
213
|
+
assert.strictEqual(containsDangerousPattern('DROP'), true);
|
|
214
|
+
assert.strictEqual(containsDangerousPattern('(DROP)'), true);
|
|
215
|
+
assert.strictEqual(containsDangerousPattern('x DROP x'), true);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
describe('validateQueryInput', () => {
|
|
219
|
+
it('should not throw for safe input', () => {
|
|
220
|
+
assert.doesNotThrow(() => validateQueryInput('hello', 'serviceName'));
|
|
221
|
+
assert.doesNotThrow(() => validateQueryInput('test-123', 'traceId'));
|
|
222
|
+
assert.doesNotThrow(() => validateQueryInput('my.service.name', 'serviceName'));
|
|
223
|
+
});
|
|
224
|
+
it('should throw for dangerous input', () => {
|
|
225
|
+
assert.throws(() => validateQueryInput('DROP TABLE', 'serviceName'), /Invalid serviceName: contains potentially dangerous SQL pattern/);
|
|
226
|
+
});
|
|
227
|
+
it('should include field name in error message', () => {
|
|
228
|
+
assert.throws(() => validateQueryInput('DROP TABLE', 'traceId'), /Invalid traceId/);
|
|
229
|
+
assert.throws(() => validateQueryInput('DELETE FROM', 'spanName'), /Invalid spanName/);
|
|
230
|
+
});
|
|
231
|
+
it('should throw for comment injection', () => {
|
|
232
|
+
assert.throws(() => validateQueryInput('value -- comment', 'search'), /Invalid search/);
|
|
233
|
+
});
|
|
234
|
+
it('should throw for semicolon injection', () => {
|
|
235
|
+
assert.throws(() => validateQueryInput('value; DROP', 'filter'), /Invalid filter/);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
describe('escapeFilterValueSafe', () => {
|
|
239
|
+
it('should escape and validate safe input', () => {
|
|
240
|
+
assert.strictEqual(escapeFilterValueSafe('hello', 'field'), 'hello');
|
|
241
|
+
assert.strictEqual(escapeFilterValueSafe("it's", 'field'), "it\\'s");
|
|
242
|
+
});
|
|
243
|
+
it('should throw for dangerous input', () => {
|
|
244
|
+
assert.throws(() => escapeFilterValueSafe('DROP TABLE', 'serviceName'), /Invalid serviceName/);
|
|
245
|
+
});
|
|
246
|
+
it('should escape special characters in safe input', () => {
|
|
247
|
+
assert.strictEqual(escapeFilterValueSafe("test\nvalue", 'field'), 'test\\nvalue');
|
|
248
|
+
assert.strictEqual(escapeFilterValueSafe('test\\path', 'field'), 'test\\\\path');
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
describe('escapeLikeValueSafe', () => {
|
|
252
|
+
it('should escape LIKE wildcards and validate', () => {
|
|
253
|
+
// 100% -> escapeClickHouseLike -> 100\% -> escapeClickHouseString -> 100\\%
|
|
254
|
+
assert.strictEqual(escapeLikeValueSafe('100%', 'field'), '100\\\\%');
|
|
255
|
+
// test_value -> escapeClickHouseLike -> test\_value -> escapeClickHouseString -> test\\_value
|
|
256
|
+
assert.strictEqual(escapeLikeValueSafe('test_value', 'field'), 'test\\\\_value');
|
|
257
|
+
});
|
|
258
|
+
it('should throw for dangerous input', () => {
|
|
259
|
+
assert.throws(() => escapeLikeValueSafe('UNION SELECT', 'search'), /Invalid search/);
|
|
260
|
+
});
|
|
261
|
+
it('should handle combined escaping', () => {
|
|
262
|
+
// Input: test%'value
|
|
263
|
+
// After LIKE escape: test\%'value (% escaped for LIKE)
|
|
264
|
+
// After string escape: test\\%\'value (\ and ' escaped for string)
|
|
265
|
+
const result = escapeLikeValueSafe("test%'value", 'field');
|
|
266
|
+
assert.strictEqual(result, "test\\\\%\\'value");
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
describe('edge cases', () => {
|
|
270
|
+
it('should handle strings at max length boundary', () => {
|
|
271
|
+
const longString = 'a'.repeat(10000);
|
|
272
|
+
assert.strictEqual(escapeClickHouseString(longString), longString);
|
|
273
|
+
// Exactly at MAX_QUERY_INPUT_LENGTH (10000) should pass
|
|
274
|
+
assert.strictEqual(containsDangerousPattern(longString), false);
|
|
275
|
+
});
|
|
276
|
+
it('should reject strings exceeding max length as dangerous (defense in depth)', () => {
|
|
277
|
+
const tooLongString = 'a'.repeat(10001);
|
|
278
|
+
// Exceeds MAX_QUERY_INPUT_LENGTH - treated as dangerous for defense in depth
|
|
279
|
+
assert.strictEqual(containsDangerousPattern(tooLongString), true);
|
|
280
|
+
});
|
|
281
|
+
it('should reject very long strings even if they contain safe content', () => {
|
|
282
|
+
// 15K of safe characters should still be rejected
|
|
283
|
+
const safeButLong = 'safe-string-'.repeat(1250);
|
|
284
|
+
assert.strictEqual(containsDangerousPattern(safeButLong), true);
|
|
285
|
+
});
|
|
286
|
+
it('should handle strings with only special characters', () => {
|
|
287
|
+
assert.strictEqual(escapeClickHouseString("'''"), "\\'\\'\\'");
|
|
288
|
+
assert.strictEqual(escapeClickHouseLike('%%%'), '\\%\\%\\%');
|
|
289
|
+
});
|
|
290
|
+
it('should handle null byte injection attempts', () => {
|
|
291
|
+
assert.strictEqual(escapeClickHouseString("test\0'DROP"), "test\\0\\'DROP");
|
|
292
|
+
});
|
|
293
|
+
it('should handle mixed case dangerous patterns', () => {
|
|
294
|
+
assert.strictEqual(containsDangerousPattern('DrOp TaBlE'), true);
|
|
295
|
+
assert.strictEqual(containsDangerousPattern('uNiOn SeLeCt'), true);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
//# sourceMappingURL=query-sanitizer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-sanitizer.test.js","sourceRoot":"","sources":["../../src/lib/query-sanitizer.test.ts"],"names":[],"mappings":"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"}
|