@weave_protocol/mund 0.1.0
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/dist/analyzers/code-analyzer.d.ts +24 -0
- package/dist/analyzers/code-analyzer.d.ts.map +1 -0
- package/dist/analyzers/code-analyzer.js +73 -0
- package/dist/analyzers/code-analyzer.js.map +1 -0
- package/dist/analyzers/exfiltration-detector.d.ts +40 -0
- package/dist/analyzers/exfiltration-detector.d.ts.map +1 -0
- package/dist/analyzers/exfiltration-detector.js +220 -0
- package/dist/analyzers/exfiltration-detector.js.map +1 -0
- package/dist/analyzers/index.d.ts +26 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +61 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/analyzers/injection-detector.d.ts +36 -0
- package/dist/analyzers/injection-detector.d.ts.map +1 -0
- package/dist/analyzers/injection-detector.js +165 -0
- package/dist/analyzers/injection-detector.js.map +1 -0
- package/dist/analyzers/pii-detector.d.ts +44 -0
- package/dist/analyzers/pii-detector.d.ts.map +1 -0
- package/dist/analyzers/pii-detector.js +190 -0
- package/dist/analyzers/pii-detector.js.map +1 -0
- package/dist/analyzers/secret-scanner.d.ts +32 -0
- package/dist/analyzers/secret-scanner.d.ts.map +1 -0
- package/dist/analyzers/secret-scanner.js +119 -0
- package/dist/analyzers/secret-scanner.js.map +1 -0
- package/dist/constants.d.ts +18 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +580 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +242 -0
- package/dist/index.js.map +1 -0
- package/dist/notifications/email.d.ts +18 -0
- package/dist/notifications/email.d.ts.map +1 -0
- package/dist/notifications/email.js +192 -0
- package/dist/notifications/email.js.map +1 -0
- package/dist/notifications/index.d.ts +51 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +107 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/slack.d.ts +17 -0
- package/dist/notifications/slack.d.ts.map +1 -0
- package/dist/notifications/slack.js +128 -0
- package/dist/notifications/slack.js.map +1 -0
- package/dist/notifications/teams.d.ts +17 -0
- package/dist/notifications/teams.d.ts.map +1 -0
- package/dist/notifications/teams.js +136 -0
- package/dist/notifications/teams.js.map +1 -0
- package/dist/notifications/webhook.d.ts +15 -0
- package/dist/notifications/webhook.d.ts.map +1 -0
- package/dist/notifications/webhook.js +67 -0
- package/dist/notifications/webhook.js.map +1 -0
- package/dist/storage/index.d.ts +11 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +26 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/memory.d.ts +35 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +144 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/tools/configuration.d.ts +8 -0
- package/dist/tools/configuration.d.ts.map +1 -0
- package/dist/tools/configuration.js +399 -0
- package/dist/tools/configuration.js.map +1 -0
- package/dist/tools/index.d.ts +15 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +16 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/monitoring.d.ts +10 -0
- package/dist/tools/monitoring.d.ts.map +1 -0
- package/dist/tools/monitoring.js +476 -0
- package/dist/tools/monitoring.js.map +1 -0
- package/dist/types.d.ts +207 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +62 -0
- package/rules/default.yaml +189 -0
- package/src/analyzers/code-analyzer.ts +88 -0
- package/src/analyzers/exfiltration-detector.ts +257 -0
- package/src/analyzers/index.ts +74 -0
- package/src/analyzers/injection-detector.ts +191 -0
- package/src/analyzers/pii-detector.ts +220 -0
- package/src/analyzers/secret-scanner.ts +142 -0
- package/src/constants.ts +603 -0
- package/src/index.ts +285 -0
- package/src/notifications/email.ts +211 -0
- package/src/notifications/index.ts +136 -0
- package/src/notifications/slack.ts +170 -0
- package/src/notifications/teams.ts +200 -0
- package/src/notifications/webhook.ts +80 -0
- package/src/storage/index.ts +29 -0
- package/src/storage/memory.ts +181 -0
- package/src/tools/configuration.ts +478 -0
- package/src/tools/index.ts +29 -0
- package/src/tools/monitoring.ts +559 -0
- package/src/types.ts +300 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mund - The Guardian Protocol
|
|
3
|
+
* Exfiltration Detector - Detects data exfiltration attempts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type DetectionRule,
|
|
8
|
+
type SecurityIssue,
|
|
9
|
+
type IAnalyzer,
|
|
10
|
+
type IssueLocation
|
|
11
|
+
} from '../types.js';
|
|
12
|
+
import { DANGEROUS_URL_PATTERNS } from '../constants.js';
|
|
13
|
+
|
|
14
|
+
export class ExfiltrationDetector implements IAnalyzer {
|
|
15
|
+
name = 'ExfiltrationDetector';
|
|
16
|
+
type = 'exfiltration';
|
|
17
|
+
|
|
18
|
+
async analyze(content: string, rules: DetectionRule[]): Promise<SecurityIssue[]> {
|
|
19
|
+
const issues: SecurityIssue[] = [];
|
|
20
|
+
const exfilRules = rules.filter(r => r.type === 'exfiltration' && r.enabled);
|
|
21
|
+
|
|
22
|
+
// Pattern-based detection
|
|
23
|
+
for (const rule of exfilRules) {
|
|
24
|
+
if (!rule.pattern) continue;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const regex = new RegExp(rule.pattern, 'gi');
|
|
28
|
+
let match: RegExpExecArray | null;
|
|
29
|
+
|
|
30
|
+
while ((match = regex.exec(content)) !== null) {
|
|
31
|
+
const matchText = match[0];
|
|
32
|
+
const location = this.getLocation(content, match.index, matchText.length);
|
|
33
|
+
|
|
34
|
+
issues.push({
|
|
35
|
+
rule_id: rule.id,
|
|
36
|
+
rule_name: rule.name,
|
|
37
|
+
type: rule.type,
|
|
38
|
+
severity: rule.severity,
|
|
39
|
+
action: rule.action,
|
|
40
|
+
match: this.truncateMatch(matchText),
|
|
41
|
+
location,
|
|
42
|
+
suggestion: this.getSuggestion(rule.id)
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`Invalid regex pattern in rule ${rule.id}:`, error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// URL extraction and analysis
|
|
51
|
+
const urlIssues = this.analyzeURLs(content);
|
|
52
|
+
issues.push(...urlIssues);
|
|
53
|
+
|
|
54
|
+
// Data encoding detection
|
|
55
|
+
const encodingIssues = this.detectSuspiciousEncoding(content);
|
|
56
|
+
issues.push(...encodingIssues);
|
|
57
|
+
|
|
58
|
+
return issues;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Extract and analyze URLs in content
|
|
63
|
+
*/
|
|
64
|
+
private analyzeURLs(content: string): SecurityIssue[] {
|
|
65
|
+
const issues: SecurityIssue[] = [];
|
|
66
|
+
|
|
67
|
+
// URL regex pattern
|
|
68
|
+
const urlRegex = /https?:\/\/[^\s"'<>)}\]]+/gi;
|
|
69
|
+
let match: RegExpExecArray | null;
|
|
70
|
+
|
|
71
|
+
while ((match = urlRegex.exec(content)) !== null) {
|
|
72
|
+
const url = match[0];
|
|
73
|
+
const location = this.getLocation(content, match.index, url.length);
|
|
74
|
+
|
|
75
|
+
// Check against dangerous patterns
|
|
76
|
+
for (const pattern of DANGEROUS_URL_PATTERNS) {
|
|
77
|
+
if (pattern.test(url)) {
|
|
78
|
+
issues.push({
|
|
79
|
+
rule_id: 'dangerous_url',
|
|
80
|
+
rule_name: 'Dangerous URL Detected',
|
|
81
|
+
type: 'exfiltration',
|
|
82
|
+
severity: 'high',
|
|
83
|
+
action: 'alert',
|
|
84
|
+
match: this.truncateURL(url),
|
|
85
|
+
location,
|
|
86
|
+
suggestion: 'This URL points to a service commonly used for data exfiltration. Verify the destination is legitimate.'
|
|
87
|
+
});
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check for IP-based URLs
|
|
93
|
+
if (/^https?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.test(url)) {
|
|
94
|
+
issues.push({
|
|
95
|
+
rule_id: 'ip_based_url',
|
|
96
|
+
rule_name: 'IP-Based URL',
|
|
97
|
+
type: 'exfiltration',
|
|
98
|
+
severity: 'medium',
|
|
99
|
+
action: 'alert',
|
|
100
|
+
match: this.truncateURL(url),
|
|
101
|
+
location,
|
|
102
|
+
suggestion: 'IP-based URLs can bypass domain-based security controls. Verify this is a legitimate destination.'
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check for data URLs
|
|
107
|
+
if (/^data:/i.test(url)) {
|
|
108
|
+
issues.push({
|
|
109
|
+
rule_id: 'data_url',
|
|
110
|
+
rule_name: 'Data URL Detected',
|
|
111
|
+
type: 'exfiltration',
|
|
112
|
+
severity: 'medium',
|
|
113
|
+
action: 'alert',
|
|
114
|
+
match: this.truncateURL(url),
|
|
115
|
+
location,
|
|
116
|
+
suggestion: 'Data URLs can encode and transfer arbitrary content. Review the embedded data.'
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return issues;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Detect suspicious data encoding patterns
|
|
126
|
+
*/
|
|
127
|
+
private detectSuspiciousEncoding(content: string): SecurityIssue[] {
|
|
128
|
+
const issues: SecurityIssue[] = [];
|
|
129
|
+
|
|
130
|
+
// Large base64 blocks (potential data exfiltration)
|
|
131
|
+
const base64Regex = /[A-Za-z0-9+/]{100,}={0,2}/g;
|
|
132
|
+
let match: RegExpExecArray | null;
|
|
133
|
+
|
|
134
|
+
while ((match = base64Regex.exec(content)) !== null) {
|
|
135
|
+
const encoded = match[0];
|
|
136
|
+
|
|
137
|
+
// Verify it's likely base64 (not just a long alphanumeric string)
|
|
138
|
+
if (this.isLikelyBase64(encoded)) {
|
|
139
|
+
const location = this.getLocation(content, match.index, encoded.length);
|
|
140
|
+
|
|
141
|
+
issues.push({
|
|
142
|
+
rule_id: 'large_base64_block',
|
|
143
|
+
rule_name: 'Large Base64 Block',
|
|
144
|
+
type: 'exfiltration',
|
|
145
|
+
severity: 'medium',
|
|
146
|
+
action: 'log',
|
|
147
|
+
match: `Base64 block (${encoded.length} chars)`,
|
|
148
|
+
location,
|
|
149
|
+
suggestion: 'Large base64 encoded blocks may contain exfiltrated data. Review the decoded content.'
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Hex-encoded data blocks
|
|
155
|
+
const hexRegex = /(?:0x)?[0-9a-fA-F]{200,}/g;
|
|
156
|
+
while ((match = hexRegex.exec(content)) !== null) {
|
|
157
|
+
const encoded = match[0];
|
|
158
|
+
const location = this.getLocation(content, match.index, encoded.length);
|
|
159
|
+
|
|
160
|
+
issues.push({
|
|
161
|
+
rule_id: 'large_hex_block',
|
|
162
|
+
rule_name: 'Large Hex-Encoded Block',
|
|
163
|
+
type: 'exfiltration',
|
|
164
|
+
severity: 'low',
|
|
165
|
+
action: 'log',
|
|
166
|
+
match: `Hex block (${encoded.length} chars)`,
|
|
167
|
+
location,
|
|
168
|
+
suggestion: 'Large hex-encoded blocks may contain encoded data. Review the decoded content.'
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return issues;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check if string is likely base64
|
|
177
|
+
*/
|
|
178
|
+
private isLikelyBase64(str: string): boolean {
|
|
179
|
+
// Check character distribution
|
|
180
|
+
const chars = new Set(str.replace(/=/g, ''));
|
|
181
|
+
|
|
182
|
+
// Base64 uses a specific character set
|
|
183
|
+
const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
184
|
+
for (const char of chars) {
|
|
185
|
+
if (!base64Chars.includes(char)) return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check if it decodes to valid content
|
|
189
|
+
try {
|
|
190
|
+
const decoded = Buffer.from(str, 'base64').toString('utf8');
|
|
191
|
+
// Check if decoded content has a reasonable ratio of printable characters
|
|
192
|
+
const printable = decoded.replace(/[^\x20-\x7E]/g, '').length;
|
|
193
|
+
return printable / decoded.length > 0.7;
|
|
194
|
+
} catch {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Calculate location within content
|
|
201
|
+
*/
|
|
202
|
+
private getLocation(content: string, start: number, length: number): IssueLocation {
|
|
203
|
+
const end = start + length;
|
|
204
|
+
const lines = content.substring(0, start).split('\n');
|
|
205
|
+
const line = lines.length;
|
|
206
|
+
const column = lines[lines.length - 1].length + 1;
|
|
207
|
+
return { start, end, line, column };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Truncate match for display
|
|
212
|
+
*/
|
|
213
|
+
private truncateMatch(match: string, maxLength = 100): string {
|
|
214
|
+
if (match.length <= maxLength) return match;
|
|
215
|
+
return match.substring(0, maxLength) + '...';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Truncate URL for display (hide potential sensitive parts)
|
|
220
|
+
*/
|
|
221
|
+
private truncateURL(url: string, maxLength = 80): string {
|
|
222
|
+
try {
|
|
223
|
+
const parsed = new URL(url);
|
|
224
|
+
const base = `${parsed.protocol}//${parsed.host}`;
|
|
225
|
+
|
|
226
|
+
if (base.length >= maxLength) {
|
|
227
|
+
return base.substring(0, maxLength) + '...';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const remainingLength = maxLength - base.length;
|
|
231
|
+
const path = parsed.pathname + parsed.search;
|
|
232
|
+
|
|
233
|
+
if (path.length > remainingLength) {
|
|
234
|
+
return base + path.substring(0, remainingLength) + '...';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return base + path;
|
|
238
|
+
} catch {
|
|
239
|
+
return url.substring(0, maxLength) + (url.length > maxLength ? '...' : '');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get remediation suggestion
|
|
245
|
+
*/
|
|
246
|
+
private getSuggestion(ruleId: string): string {
|
|
247
|
+
const suggestions: Record<string, string> = {
|
|
248
|
+
suspicious_url_post: 'This code sends data to a service commonly used for data capture. Verify the destination.',
|
|
249
|
+
dns_exfiltration: 'DNS queries with encoded data can be used for exfiltration. Monitor and restrict DNS access.',
|
|
250
|
+
data_encoding_suspicious: 'Encoding sensitive data before transmission may indicate exfiltration. Review the purpose.',
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return suggestions[ruleId] || 'Review this pattern for potential data exfiltration.';
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export default ExfiltrationDetector;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mund - The Guardian Protocol
|
|
3
|
+
* Analyzers Index - Exports all analyzer modules
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { SecretScanner } from './secret-scanner.js';
|
|
7
|
+
export { PIIDetector } from './pii-detector.js';
|
|
8
|
+
export { CodeAnalyzer } from './code-analyzer.js';
|
|
9
|
+
export { InjectionDetector } from './injection-detector.js';
|
|
10
|
+
export { ExfiltrationDetector } from './exfiltration-detector.js';
|
|
11
|
+
|
|
12
|
+
import { SecretScanner } from './secret-scanner.js';
|
|
13
|
+
import { PIIDetector } from './pii-detector.js';
|
|
14
|
+
import { CodeAnalyzer } from './code-analyzer.js';
|
|
15
|
+
import { InjectionDetector } from './injection-detector.js';
|
|
16
|
+
import { ExfiltrationDetector } from './exfiltration-detector.js';
|
|
17
|
+
import type { IAnalyzer } from '../types.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get all available analyzers
|
|
21
|
+
*/
|
|
22
|
+
export function getAnalyzers(): IAnalyzer[] {
|
|
23
|
+
return [
|
|
24
|
+
new SecretScanner(),
|
|
25
|
+
new PIIDetector(),
|
|
26
|
+
new CodeAnalyzer(),
|
|
27
|
+
new InjectionDetector(),
|
|
28
|
+
new ExfiltrationDetector()
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create analyzer engine that runs all analyzers
|
|
34
|
+
*/
|
|
35
|
+
export class AnalyzerEngine {
|
|
36
|
+
private analyzers: IAnalyzer[];
|
|
37
|
+
|
|
38
|
+
constructor(analyzers?: IAnalyzer[]) {
|
|
39
|
+
this.analyzers = analyzers || getAnalyzers();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async analyzeAll(content: string, rules: import('../types.js').DetectionRule[]): Promise<import('../types.js').SecurityIssue[]> {
|
|
43
|
+
const allIssues: import('../types.js').SecurityIssue[] = [];
|
|
44
|
+
|
|
45
|
+
for (const analyzer of this.analyzers) {
|
|
46
|
+
try {
|
|
47
|
+
const issues = await analyzer.analyze(content, rules);
|
|
48
|
+
allIssues.push(...issues);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`Error in analyzer ${analyzer.name}:`, error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Sort by severity (critical first)
|
|
55
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
56
|
+
allIssues.sort((a, b) =>
|
|
57
|
+
(severityOrder[a.severity] || 4) - (severityOrder[b.severity] || 4)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return allIssues;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
addAnalyzer(analyzer: IAnalyzer): void {
|
|
64
|
+
this.analyzers.push(analyzer);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
removeAnalyzer(name: string): void {
|
|
68
|
+
this.analyzers = this.analyzers.filter(a => a.name !== name);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getAnalyzerNames(): string[] {
|
|
72
|
+
return this.analyzers.map(a => a.name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mund - The Guardian Protocol
|
|
3
|
+
* Injection Detector - Detects prompt injection attempts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type DetectionRule,
|
|
8
|
+
type SecurityIssue,
|
|
9
|
+
type IAnalyzer,
|
|
10
|
+
type IssueLocation
|
|
11
|
+
} from '../types.js';
|
|
12
|
+
|
|
13
|
+
export class InjectionDetector implements IAnalyzer {
|
|
14
|
+
name = 'InjectionDetector';
|
|
15
|
+
type = 'injection';
|
|
16
|
+
|
|
17
|
+
async analyze(content: string, rules: DetectionRule[]): Promise<SecurityIssue[]> {
|
|
18
|
+
const issues: SecurityIssue[] = [];
|
|
19
|
+
const injectionRules = rules.filter(r => r.type === 'injection' && r.enabled);
|
|
20
|
+
|
|
21
|
+
// Normalize content for detection
|
|
22
|
+
const normalizedContent = this.normalizeContent(content);
|
|
23
|
+
|
|
24
|
+
for (const rule of injectionRules) {
|
|
25
|
+
if (!rule.pattern) continue;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const regex = new RegExp(rule.pattern, 'gi');
|
|
29
|
+
let match: RegExpExecArray | null;
|
|
30
|
+
|
|
31
|
+
while ((match = regex.exec(normalizedContent)) !== null) {
|
|
32
|
+
const matchText = match[0];
|
|
33
|
+
const location = this.getLocation(content, match.index, matchText.length);
|
|
34
|
+
|
|
35
|
+
issues.push({
|
|
36
|
+
rule_id: rule.id,
|
|
37
|
+
rule_name: rule.name,
|
|
38
|
+
type: rule.type,
|
|
39
|
+
severity: rule.severity,
|
|
40
|
+
action: rule.action,
|
|
41
|
+
match: this.sanitizeMatch(matchText),
|
|
42
|
+
location,
|
|
43
|
+
suggestion: this.getSuggestion(rule.id)
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(`Invalid regex pattern in rule ${rule.id}:`, error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Additional heuristic checks
|
|
52
|
+
const heuristicIssues = this.heuristicAnalysis(content);
|
|
53
|
+
issues.push(...heuristicIssues);
|
|
54
|
+
|
|
55
|
+
return issues;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Normalize content to detect obfuscated injection attempts
|
|
60
|
+
*/
|
|
61
|
+
private normalizeContent(content: string): string {
|
|
62
|
+
return content
|
|
63
|
+
// Remove zero-width characters
|
|
64
|
+
.replace(/[\u200B-\u200D\uFEFF]/g, '')
|
|
65
|
+
// Normalize Unicode variations
|
|
66
|
+
.normalize('NFKC')
|
|
67
|
+
// Collapse multiple spaces
|
|
68
|
+
.replace(/\s+/g, ' ')
|
|
69
|
+
// Remove common obfuscation characters
|
|
70
|
+
.replace(/[^\w\s.,!?'"()-]/g, ' ');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Heuristic analysis for injection patterns
|
|
75
|
+
*/
|
|
76
|
+
private heuristicAnalysis(content: string): SecurityIssue[] {
|
|
77
|
+
const issues: SecurityIssue[] = [];
|
|
78
|
+
|
|
79
|
+
// Check for role switching attempts
|
|
80
|
+
const roleSwitchPatterns = [
|
|
81
|
+
/\bsystem\s*:\s*you\s+are/i,
|
|
82
|
+
/\bassistant\s*:\s*i\s+(will|can|am)/i,
|
|
83
|
+
/\[system\]/i,
|
|
84
|
+
/\<\|?system\|?\>/i,
|
|
85
|
+
/\{system_prompt\}/i,
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
for (const pattern of roleSwitchPatterns) {
|
|
89
|
+
const match = content.match(pattern);
|
|
90
|
+
if (match) {
|
|
91
|
+
issues.push({
|
|
92
|
+
rule_id: 'heuristic_role_switch',
|
|
93
|
+
rule_name: 'Role Switching Attempt',
|
|
94
|
+
type: 'injection',
|
|
95
|
+
severity: 'high' as const,
|
|
96
|
+
action: 'alert' as const,
|
|
97
|
+
match: this.sanitizeMatch(match[0]),
|
|
98
|
+
suggestion: 'This content appears to attempt to inject role-switching markers.'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check for markdown/code injection that might alter rendering
|
|
104
|
+
const markdownPatterns = [
|
|
105
|
+
/```\s*system/i,
|
|
106
|
+
/\<script\>/i,
|
|
107
|
+
/javascript:/i,
|
|
108
|
+
/data:text\/html/i,
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
for (const pattern of markdownPatterns) {
|
|
112
|
+
const match = content.match(pattern);
|
|
113
|
+
if (match) {
|
|
114
|
+
issues.push({
|
|
115
|
+
rule_id: 'heuristic_markup_injection',
|
|
116
|
+
rule_name: 'Markup Injection Attempt',
|
|
117
|
+
type: 'injection',
|
|
118
|
+
severity: 'medium' as const,
|
|
119
|
+
action: 'alert' as const,
|
|
120
|
+
match: this.sanitizeMatch(match[0]),
|
|
121
|
+
suggestion: 'This content contains potentially dangerous markup or code injection.'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check for excessive special characters (potential obfuscation)
|
|
127
|
+
const specialCharRatio = this.calculateSpecialCharRatio(content);
|
|
128
|
+
if (specialCharRatio > 0.3 && content.length > 50) {
|
|
129
|
+
issues.push({
|
|
130
|
+
rule_id: 'heuristic_obfuscation',
|
|
131
|
+
rule_name: 'Potential Obfuscation',
|
|
132
|
+
type: 'injection',
|
|
133
|
+
severity: 'low' as const,
|
|
134
|
+
action: 'log' as const,
|
|
135
|
+
match: `Special character ratio: ${(specialCharRatio * 100).toFixed(1)}%`,
|
|
136
|
+
suggestion: 'This content has an unusually high ratio of special characters, which may indicate obfuscation.'
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return issues;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Calculate ratio of special characters
|
|
145
|
+
*/
|
|
146
|
+
private calculateSpecialCharRatio(content: string): number {
|
|
147
|
+
if (content.length === 0) return 0;
|
|
148
|
+
const specialChars = content.replace(/[\w\s]/g, '').length;
|
|
149
|
+
return specialChars / content.length;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Calculate location within content
|
|
154
|
+
*/
|
|
155
|
+
private getLocation(content: string, start: number, length: number): IssueLocation {
|
|
156
|
+
const end = start + length;
|
|
157
|
+
const lines = content.substring(0, start).split('\n');
|
|
158
|
+
const line = lines.length;
|
|
159
|
+
const column = lines[lines.length - 1].length + 1;
|
|
160
|
+
return { start, end, line, column };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Sanitize match for safe display
|
|
165
|
+
*/
|
|
166
|
+
private sanitizeMatch(match: string, maxLength = 100): string {
|
|
167
|
+
const sanitized = match
|
|
168
|
+
.replace(/</g, '<')
|
|
169
|
+
.replace(/>/g, '>')
|
|
170
|
+
.replace(/"/g, '"');
|
|
171
|
+
|
|
172
|
+
if (sanitized.length <= maxLength) return sanitized;
|
|
173
|
+
return sanitized.substring(0, maxLength) + '...';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get remediation suggestion
|
|
178
|
+
*/
|
|
179
|
+
private getSuggestion(ruleId: string): string {
|
|
180
|
+
const suggestions: Record<string, string> = {
|
|
181
|
+
prompt_injection_ignore: 'This content attempts to make the AI ignore its instructions. Do not process user inputs that contain such patterns.',
|
|
182
|
+
prompt_injection_jailbreak: 'This appears to be a jailbreak attempt. Such content should be filtered or rejected.',
|
|
183
|
+
prompt_injection_system: 'This content attempts to extract system prompts. Do not reveal system instructions.',
|
|
184
|
+
prompt_injection_roleplay: 'This content attempts to manipulate behavior through roleplay. Apply content filtering.',
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
return suggestions[ruleId] || 'This content shows signs of injection attacks. Review and sanitize input.';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export default InjectionDetector;
|