@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.
Files changed (98) hide show
  1. package/dist/analyzers/code-analyzer.d.ts +24 -0
  2. package/dist/analyzers/code-analyzer.d.ts.map +1 -0
  3. package/dist/analyzers/code-analyzer.js +73 -0
  4. package/dist/analyzers/code-analyzer.js.map +1 -0
  5. package/dist/analyzers/exfiltration-detector.d.ts +40 -0
  6. package/dist/analyzers/exfiltration-detector.d.ts.map +1 -0
  7. package/dist/analyzers/exfiltration-detector.js +220 -0
  8. package/dist/analyzers/exfiltration-detector.js.map +1 -0
  9. package/dist/analyzers/index.d.ts +26 -0
  10. package/dist/analyzers/index.d.ts.map +1 -0
  11. package/dist/analyzers/index.js +61 -0
  12. package/dist/analyzers/index.js.map +1 -0
  13. package/dist/analyzers/injection-detector.d.ts +36 -0
  14. package/dist/analyzers/injection-detector.d.ts.map +1 -0
  15. package/dist/analyzers/injection-detector.js +165 -0
  16. package/dist/analyzers/injection-detector.js.map +1 -0
  17. package/dist/analyzers/pii-detector.d.ts +44 -0
  18. package/dist/analyzers/pii-detector.d.ts.map +1 -0
  19. package/dist/analyzers/pii-detector.js +190 -0
  20. package/dist/analyzers/pii-detector.js.map +1 -0
  21. package/dist/analyzers/secret-scanner.d.ts +32 -0
  22. package/dist/analyzers/secret-scanner.d.ts.map +1 -0
  23. package/dist/analyzers/secret-scanner.js +119 -0
  24. package/dist/analyzers/secret-scanner.js.map +1 -0
  25. package/dist/constants.d.ts +18 -0
  26. package/dist/constants.d.ts.map +1 -0
  27. package/dist/constants.js +580 -0
  28. package/dist/constants.js.map +1 -0
  29. package/dist/index.d.ts +9 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +242 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/notifications/email.d.ts +18 -0
  34. package/dist/notifications/email.d.ts.map +1 -0
  35. package/dist/notifications/email.js +192 -0
  36. package/dist/notifications/email.js.map +1 -0
  37. package/dist/notifications/index.d.ts +51 -0
  38. package/dist/notifications/index.d.ts.map +1 -0
  39. package/dist/notifications/index.js +107 -0
  40. package/dist/notifications/index.js.map +1 -0
  41. package/dist/notifications/slack.d.ts +17 -0
  42. package/dist/notifications/slack.d.ts.map +1 -0
  43. package/dist/notifications/slack.js +128 -0
  44. package/dist/notifications/slack.js.map +1 -0
  45. package/dist/notifications/teams.d.ts +17 -0
  46. package/dist/notifications/teams.d.ts.map +1 -0
  47. package/dist/notifications/teams.js +136 -0
  48. package/dist/notifications/teams.js.map +1 -0
  49. package/dist/notifications/webhook.d.ts +15 -0
  50. package/dist/notifications/webhook.d.ts.map +1 -0
  51. package/dist/notifications/webhook.js +67 -0
  52. package/dist/notifications/webhook.js.map +1 -0
  53. package/dist/storage/index.d.ts +11 -0
  54. package/dist/storage/index.d.ts.map +1 -0
  55. package/dist/storage/index.js +26 -0
  56. package/dist/storage/index.js.map +1 -0
  57. package/dist/storage/memory.d.ts +35 -0
  58. package/dist/storage/memory.d.ts.map +1 -0
  59. package/dist/storage/memory.js +144 -0
  60. package/dist/storage/memory.js.map +1 -0
  61. package/dist/tools/configuration.d.ts +8 -0
  62. package/dist/tools/configuration.d.ts.map +1 -0
  63. package/dist/tools/configuration.js +399 -0
  64. package/dist/tools/configuration.js.map +1 -0
  65. package/dist/tools/index.d.ts +15 -0
  66. package/dist/tools/index.d.ts.map +1 -0
  67. package/dist/tools/index.js +16 -0
  68. package/dist/tools/index.js.map +1 -0
  69. package/dist/tools/monitoring.d.ts +10 -0
  70. package/dist/tools/monitoring.d.ts.map +1 -0
  71. package/dist/tools/monitoring.js +476 -0
  72. package/dist/tools/monitoring.js.map +1 -0
  73. package/dist/types.d.ts +207 -0
  74. package/dist/types.d.ts.map +1 -0
  75. package/dist/types.js +6 -0
  76. package/dist/types.js.map +1 -0
  77. package/package.json +62 -0
  78. package/rules/default.yaml +189 -0
  79. package/src/analyzers/code-analyzer.ts +88 -0
  80. package/src/analyzers/exfiltration-detector.ts +257 -0
  81. package/src/analyzers/index.ts +74 -0
  82. package/src/analyzers/injection-detector.ts +191 -0
  83. package/src/analyzers/pii-detector.ts +220 -0
  84. package/src/analyzers/secret-scanner.ts +142 -0
  85. package/src/constants.ts +603 -0
  86. package/src/index.ts +285 -0
  87. package/src/notifications/email.ts +211 -0
  88. package/src/notifications/index.ts +136 -0
  89. package/src/notifications/slack.ts +170 -0
  90. package/src/notifications/teams.ts +200 -0
  91. package/src/notifications/webhook.ts +80 -0
  92. package/src/storage/index.ts +29 -0
  93. package/src/storage/memory.ts +181 -0
  94. package/src/tools/configuration.ts +478 -0
  95. package/src/tools/index.ts +29 -0
  96. package/src/tools/monitoring.ts +559 -0
  97. package/src/types.ts +300 -0
  98. package/tsconfig.json +24 -0
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Mund - The Guardian Protocol
3
+ * PII Detector - Detects personally identifiable information
4
+ */
5
+
6
+ import {
7
+ type DetectionRule,
8
+ type SecurityIssue,
9
+ type IAnalyzer,
10
+ type IssueLocation
11
+ } from '../types.js';
12
+
13
+ export class PIIDetector implements IAnalyzer {
14
+ name = 'PIIDetector';
15
+ type = 'pii';
16
+
17
+ async analyze(content: string, rules: DetectionRule[]): Promise<SecurityIssue[]> {
18
+ const issues: SecurityIssue[] = [];
19
+ const piiRules = rules.filter(r => r.type === 'pii' && r.enabled);
20
+
21
+ for (const rule of piiRules) {
22
+ if (!rule.pattern) continue;
23
+
24
+ try {
25
+ const regex = new RegExp(rule.pattern, 'g');
26
+ let match: RegExpExecArray | null;
27
+
28
+ while ((match = regex.exec(content)) !== null) {
29
+ const matchText = match[0];
30
+
31
+ // Additional validation for specific types
32
+ if (!this.validateMatch(rule.id, matchText)) {
33
+ continue;
34
+ }
35
+
36
+ const location = this.getLocation(content, match.index, matchText.length);
37
+
38
+ issues.push({
39
+ rule_id: rule.id,
40
+ rule_name: rule.name,
41
+ type: rule.type,
42
+ severity: rule.severity,
43
+ action: rule.action,
44
+ match: this.redactPII(rule.id, matchText),
45
+ location,
46
+ suggestion: this.getSuggestion(rule.id)
47
+ });
48
+ }
49
+ } catch (error) {
50
+ console.error(`Invalid regex pattern in rule ${rule.id}:`, error);
51
+ }
52
+ }
53
+
54
+ return issues;
55
+ }
56
+
57
+ /**
58
+ * Additional validation for matches to reduce false positives
59
+ */
60
+ private validateMatch(ruleId: string, match: string): boolean {
61
+ switch (ruleId) {
62
+ case 'ssn_us':
63
+ return this.validateSSN(match);
64
+ case 'credit_card':
65
+ return this.validateCreditCard(match);
66
+ case 'email_address':
67
+ return this.validateEmail(match);
68
+ case 'ip_address':
69
+ return this.validateIPAddress(match);
70
+ default:
71
+ return true;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Validate SSN format (basic validation, not checking if real)
77
+ */
78
+ private validateSSN(ssn: string): boolean {
79
+ const digits = ssn.replace(/\D/g, '');
80
+ if (digits.length !== 9) return false;
81
+
82
+ // SSNs cannot start with 000, 666, or 900-999
83
+ const area = parseInt(digits.substring(0, 3));
84
+ if (area === 0 || area === 666 || area >= 900) return false;
85
+
86
+ // Group number cannot be 00
87
+ const group = parseInt(digits.substring(3, 5));
88
+ if (group === 0) return false;
89
+
90
+ // Serial number cannot be 0000
91
+ const serial = parseInt(digits.substring(5));
92
+ if (serial === 0) return false;
93
+
94
+ return true;
95
+ }
96
+
97
+ /**
98
+ * Validate credit card using Luhn algorithm
99
+ */
100
+ private validateCreditCard(cc: string): boolean {
101
+ const digits = cc.replace(/\D/g, '');
102
+ if (digits.length < 13 || digits.length > 19) return false;
103
+
104
+ let sum = 0;
105
+ let isEven = false;
106
+
107
+ for (let i = digits.length - 1; i >= 0; i--) {
108
+ let digit = parseInt(digits[i]);
109
+
110
+ if (isEven) {
111
+ digit *= 2;
112
+ if (digit > 9) {
113
+ digit -= 9;
114
+ }
115
+ }
116
+
117
+ sum += digit;
118
+ isEven = !isEven;
119
+ }
120
+
121
+ return sum % 10 === 0;
122
+ }
123
+
124
+ /**
125
+ * Validate email address format
126
+ */
127
+ private validateEmail(email: string): boolean {
128
+ // Check for common false positives
129
+ const falsePaths = ['.js', '.ts', '.py', '.java', '.cpp', '.go', '.rs'];
130
+ if (falsePaths.some(ext => email.endsWith(ext))) return false;
131
+
132
+ // Check for reasonable length
133
+ if (email.length > 254) return false;
134
+
135
+ // Check for valid TLD (at least 2 chars)
136
+ const parts = email.split('.');
137
+ const tld = parts[parts.length - 1];
138
+ if (tld.length < 2) return false;
139
+
140
+ return true;
141
+ }
142
+
143
+ /**
144
+ * Validate IP address (exclude obvious non-IPs)
145
+ */
146
+ private validateIPAddress(ip: string): boolean {
147
+ // Exclude version numbers (e.g., 1.2.3.4 in software versions)
148
+ const parts = ip.split('.');
149
+
150
+ // All parts should be valid octets
151
+ for (const part of parts) {
152
+ const num = parseInt(part);
153
+ if (num < 0 || num > 255) return false;
154
+ }
155
+
156
+ // Exclude common version patterns
157
+ if (parts[0] === '0' || parts[0] === '1') {
158
+ if (parseInt(parts[1]) <= 20 && parseInt(parts[2]) <= 20) {
159
+ return false; // Likely a version number
160
+ }
161
+ }
162
+
163
+ // Exclude localhost and broadcast
164
+ if (ip === '127.0.0.1' || ip === '0.0.0.0' || ip === '255.255.255.255') {
165
+ return false;
166
+ }
167
+
168
+ return true;
169
+ }
170
+
171
+ /**
172
+ * Calculate location within content
173
+ */
174
+ private getLocation(content: string, start: number, length: number): IssueLocation {
175
+ const end = start + length;
176
+ const lines = content.substring(0, start).split('\n');
177
+ const line = lines.length;
178
+ const column = lines[lines.length - 1].length + 1;
179
+ return { start, end, line, column };
180
+ }
181
+
182
+ /**
183
+ * Redact PII for safe display
184
+ */
185
+ private redactPII(ruleId: string, value: string): string {
186
+ switch (ruleId) {
187
+ case 'ssn_us':
188
+ return `***-**-${value.slice(-4)}`;
189
+ case 'credit_card':
190
+ return `****-****-****-${value.slice(-4)}`;
191
+ case 'email_address':
192
+ const [local, domain] = value.split('@');
193
+ return `${local.charAt(0)}***@${domain}`;
194
+ case 'phone_number_us':
195
+ return `***-***-${value.slice(-4)}`;
196
+ case 'ip_address':
197
+ const parts = value.split('.');
198
+ return `${parts[0]}.***.***.*${parts[3].slice(-1)}`;
199
+ default:
200
+ return value.substring(0, 3) + '***' + value.slice(-2);
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Get remediation suggestion
206
+ */
207
+ private getSuggestion(ruleId: string): string {
208
+ const suggestions: Record<string, string> = {
209
+ ssn_us: 'Social Security Numbers should never be stored in plain text. Use encryption or tokenization.',
210
+ credit_card: 'Credit card numbers must be handled according to PCI DSS standards. Use tokenization.',
211
+ email_address: 'Consider whether email addresses need to be logged. Hash or encrypt if stored.',
212
+ phone_number_us: 'Phone numbers are PII. Consider whether they need to be included.',
213
+ ip_address: 'IP addresses can be PII under GDPR. Consider hashing or anonymizing.'
214
+ };
215
+
216
+ return suggestions[ruleId] || 'Review whether this PII needs to be included and handle according to privacy policies.';
217
+ }
218
+ }
219
+
220
+ export default PIIDetector;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Mund - The Guardian Protocol
3
+ * Secret Scanner - Detects secrets, API keys, and credentials
4
+ */
5
+
6
+ import {
7
+ type DetectionRule,
8
+ type SecurityIssue,
9
+ type IAnalyzer,
10
+ type IssueLocation
11
+ } from '../types.js';
12
+
13
+ export class SecretScanner implements IAnalyzer {
14
+ name = 'SecretScanner';
15
+ type = 'secret';
16
+
17
+ async analyze(content: string, rules: DetectionRule[]): Promise<SecurityIssue[]> {
18
+ const issues: SecurityIssue[] = [];
19
+ const secretRules = rules.filter(r => r.type === 'secret' && r.enabled);
20
+
21
+ for (const rule of secretRules) {
22
+ if (!rule.pattern) continue;
23
+
24
+ try {
25
+ const regex = new RegExp(rule.pattern, 'g');
26
+ let match: RegExpExecArray | null;
27
+
28
+ while ((match = regex.exec(content)) !== null) {
29
+ const matchText = match[0];
30
+ const location = this.getLocation(content, match.index, matchText.length);
31
+
32
+ issues.push({
33
+ rule_id: rule.id,
34
+ rule_name: rule.name,
35
+ type: rule.type,
36
+ severity: rule.severity,
37
+ action: rule.action,
38
+ match: this.redactSecret(matchText),
39
+ location,
40
+ suggestion: this.getSuggestion(rule.id)
41
+ });
42
+ }
43
+ } catch (error) {
44
+ // Invalid regex pattern - skip this rule
45
+ console.error(`Invalid regex pattern in rule ${rule.id}:`, error);
46
+ }
47
+ }
48
+
49
+ return issues;
50
+ }
51
+
52
+ /**
53
+ * Calculate the location of a match within the content
54
+ */
55
+ private getLocation(content: string, start: number, length: number): IssueLocation {
56
+ const end = start + length;
57
+
58
+ // Calculate line and column
59
+ const lines = content.substring(0, start).split('\n');
60
+ const line = lines.length;
61
+ const column = lines[lines.length - 1].length + 1;
62
+
63
+ return { start, end, line, column };
64
+ }
65
+
66
+ /**
67
+ * Redact the middle portion of a secret for safe display
68
+ */
69
+ private redactSecret(secret: string): string {
70
+ if (secret.length <= 8) {
71
+ return '****';
72
+ }
73
+
74
+ const visibleStart = Math.min(4, Math.floor(secret.length * 0.2));
75
+ const visibleEnd = Math.min(4, Math.floor(secret.length * 0.2));
76
+ const redactedLength = secret.length - visibleStart - visibleEnd;
77
+
78
+ return `${secret.substring(0, visibleStart)}${'*'.repeat(Math.min(redactedLength, 20))}${secret.substring(secret.length - visibleEnd)}`;
79
+ }
80
+
81
+ /**
82
+ * Get remediation suggestion for a specific rule
83
+ */
84
+ private getSuggestion(ruleId: string): string {
85
+ const suggestions: Record<string, string> = {
86
+ aws_access_key_id: 'Use AWS IAM roles or environment variables instead of hardcoding credentials.',
87
+ aws_secret_access_key: 'Never commit AWS secret keys. Use AWS Secrets Manager or environment variables.',
88
+ github_token: 'Use GitHub Actions secrets or environment variables for tokens.',
89
+ github_fine_grained_token: 'Store GitHub tokens in a secrets manager, not in code.',
90
+ openai_api_key: 'Use environment variables (OPENAI_API_KEY) instead of hardcoding.',
91
+ anthropic_api_key: 'Use environment variables (ANTHROPIC_API_KEY) instead of hardcoding.',
92
+ google_api_key: 'Restrict API key usage in Google Cloud Console and use environment variables.',
93
+ slack_token: 'Store Slack tokens in environment variables or a secrets manager.',
94
+ slack_webhook: 'Webhook URLs should be stored in environment variables.',
95
+ stripe_api_key: 'Use environment variables. Never expose live keys in client-side code.',
96
+ private_key_block: 'Private keys should never be in code. Use a key management service.',
97
+ jwt_token: 'JWTs should be transmitted securely and never logged or stored in code.',
98
+ database_url: 'Database credentials should be in environment variables or a secrets manager.',
99
+ generic_api_key: 'API keys should be stored in environment variables or a secrets manager.',
100
+ generic_password: 'Passwords should never be hardcoded. Use a secrets manager.',
101
+ };
102
+
103
+ return suggestions[ruleId] || 'Remove this secret from code and use a secrets manager or environment variables.';
104
+ }
105
+
106
+ /**
107
+ * Calculate Shannon entropy of a string (higher = more random = more likely a secret)
108
+ */
109
+ static calculateEntropy(str: string): number {
110
+ const len = str.length;
111
+ if (len === 0) return 0;
112
+
113
+ const frequencies: Record<string, number> = {};
114
+ for (const char of str) {
115
+ frequencies[char] = (frequencies[char] || 0) + 1;
116
+ }
117
+
118
+ let entropy = 0;
119
+ for (const count of Object.values(frequencies)) {
120
+ const p = count / len;
121
+ entropy -= p * Math.log2(p);
122
+ }
123
+
124
+ return entropy;
125
+ }
126
+
127
+ /**
128
+ * Check if a string is likely a high-entropy secret
129
+ */
130
+ static isHighEntropy(str: string, threshold = 4.5): boolean {
131
+ // Skip strings that are too short or too long
132
+ if (str.length < 16 || str.length > 200) return false;
133
+
134
+ // Skip if it looks like a common word or path
135
+ if (/^[a-z]+$/i.test(str)) return false;
136
+ if (str.includes('/') && !str.includes('://')) return false;
137
+
138
+ return SecretScanner.calculateEntropy(str) > threshold;
139
+ }
140
+ }
141
+
142
+ export default SecretScanner;