code-sentinel-mcp 0.2.4 → 0.2.6

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.
@@ -0,0 +1,20 @@
1
+ import { MatchConfig, EmptyBlockConfig, FunctionCallConfig, ReturnsOnlyConfig, ContainsTextConfig, FallbackValueConfig, AssignmentPatternConfig, ChainedAccessConfig, CatchHandlerConfig, PromiseCatchConfig, CommentMarkerConfig, StringLiteralConfig, SecretPatternConfig, UrlPatternConfig, SuppressionCommentConfig, TypeCastConfig, ComparisonConfig, LoopPatternConfig, RawRegexConfig } from './types.js';
2
+ export declare function buildEmptyBlock(config: EmptyBlockConfig): RegExp[];
3
+ export declare function buildFunctionCall(config: FunctionCallConfig): RegExp[];
4
+ export declare function buildReturnsOnly(config: ReturnsOnlyConfig): RegExp[];
5
+ export declare function buildContainsText(config: ContainsTextConfig): RegExp[];
6
+ export declare function buildFallbackValue(config: FallbackValueConfig): RegExp[];
7
+ export declare function buildAssignmentPattern(config: AssignmentPatternConfig): RegExp[];
8
+ export declare function buildChainedAccess(config: ChainedAccessConfig): RegExp[];
9
+ export declare function buildCatchHandler(config: CatchHandlerConfig): RegExp[];
10
+ export declare function buildPromiseCatch(config: PromiseCatchConfig): RegExp[];
11
+ export declare function buildCommentMarker(config: CommentMarkerConfig): RegExp[];
12
+ export declare function buildStringLiteral(config: StringLiteralConfig): RegExp[];
13
+ export declare function buildSecretPattern(config: SecretPatternConfig): RegExp[];
14
+ export declare function buildUrlPattern(config: UrlPatternConfig): RegExp[];
15
+ export declare function buildSuppressionComment(config: SuppressionCommentConfig): RegExp[];
16
+ export declare function buildTypeCast(config: TypeCastConfig): RegExp[];
17
+ export declare function buildComparison(config: ComparisonConfig): RegExp[];
18
+ export declare function buildLoopPattern(config: LoopPatternConfig): RegExp[];
19
+ export declare function buildRawRegex(config: RawRegexConfig): RegExp[];
20
+ export declare function buildPattern(config: MatchConfig): RegExp[];
@@ -0,0 +1,335 @@
1
+ // Pattern builders - transform declarative configs into regex
2
+ // Utility: escape special regex characters
3
+ function escapeRegex(str) {
4
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
5
+ }
6
+ // Utility: convert value shorthand to regex pattern
7
+ function valueToPattern(value) {
8
+ const valueMap = {
9
+ '[]': '\\[\\s*\\]',
10
+ '{}': '\\{\\s*\\}',
11
+ 'null': 'null',
12
+ 'undefined': 'undefined',
13
+ 'true': 'true',
14
+ 'false': 'false',
15
+ '""': '["\']\\s*["\']',
16
+ "''": '["\']\\s*["\']',
17
+ '0': '0',
18
+ '-1': '-1',
19
+ };
20
+ return valueMap[value] || escapeRegex(value);
21
+ }
22
+ // Builder: Empty blocks (catch, finally, .catch, .then)
23
+ export function buildEmptyBlock(config) {
24
+ const results = [];
25
+ for (const construct of config.constructs) {
26
+ if (construct === 'catch') {
27
+ // try-catch style: catch(e) { }
28
+ results.push(/catch\s*\([^)]*\)\s*\{\s*\}/g);
29
+ if (config.allowComments) {
30
+ results.push(/catch\s*\([^)]*\)\s*\{\s*\/\/[^\n]*\s*\}/g);
31
+ results.push(/catch\s*\([^)]*\)\s*\{\s*\/\*[\s\S]*?\*\/\s*\}/g);
32
+ }
33
+ }
34
+ if (construct === 'finally') {
35
+ results.push(/finally\s*\{\s*\}/g);
36
+ }
37
+ if (construct === '.catch') {
38
+ // Promise style: .catch(() => {})
39
+ results.push(/\.catch\s*\(\s*\(\s*\)\s*=>\s*\{\s*\}\s*\)/g);
40
+ results.push(/\.catch\s*\(\s*\w+\s*=>\s*\{\s*\}\s*\)/g);
41
+ results.push(/\.catch\s*\(\s*function\s*\([^)]*\)\s*\{\s*\}\s*\)/g);
42
+ }
43
+ if (construct === '.then') {
44
+ results.push(/\.then\s*\(\s*\(\s*\)\s*=>\s*\{\s*\}\s*\)/g);
45
+ results.push(/\.then\s*\(\s*\w+\s*=>\s*\{\s*\}\s*\)/g);
46
+ }
47
+ if (construct === '.finally') {
48
+ results.push(/\.finally\s*\(\s*\(\s*\)\s*=>\s*\{\s*\}\s*\)/g);
49
+ }
50
+ }
51
+ return results;
52
+ }
53
+ // Builder: Function calls
54
+ export function buildFunctionCall(config) {
55
+ const results = [];
56
+ const escaped = config.names.map(n => escapeRegex(n));
57
+ const namesPattern = escaped.join('|');
58
+ // Direct call: eval(), Function()
59
+ results.push(new RegExp(`\\b(${namesPattern})\\s*\\(`, 'gi'));
60
+ // Method call: obj.eval() if enabled
61
+ if (config.methods) {
62
+ results.push(new RegExp(`\\.(${namesPattern})\\s*\\(`, 'gi'));
63
+ }
64
+ // Constructor: new Function() if enabled
65
+ if (config.constructors) {
66
+ results.push(new RegExp(`new\\s+(${namesPattern})\\s*\\(`, 'gi'));
67
+ }
68
+ return results;
69
+ }
70
+ // Builder: Return statements with specific values
71
+ export function buildReturnsOnly(config) {
72
+ const results = [];
73
+ const valuePatterns = config.values.map(valueToPattern);
74
+ const valuesPattern = valuePatterns.join('|');
75
+ if (config.withComment && config.withComment.length > 0) {
76
+ // return value; // comment with specific words
77
+ const commentWords = config.withComment.join('|');
78
+ results.push(new RegExp(`return\\s+(${valuesPattern})\\s*;?\\s*\\/\\/\\s*(?:.*?)(${commentWords})`, 'gi'));
79
+ }
80
+ else {
81
+ // Just return value;
82
+ results.push(new RegExp(`return\\s+(${valuesPattern})\\s*;?`, 'gi'));
83
+ }
84
+ return results;
85
+ }
86
+ // Builder: Text search in context
87
+ export function buildContainsText(config) {
88
+ const results = [];
89
+ const flags = config.caseInsensitive !== false ? 'gi' : 'g';
90
+ const termsPattern = config.terms.map(escapeRegex).join('|');
91
+ const context = config.context || 'any';
92
+ if (context === 'single_line_comment' || context === 'comment' || context === 'any') {
93
+ results.push(new RegExp(`\\/\\/.*?(${termsPattern})`, flags));
94
+ }
95
+ if (context === 'block_comment' || context === 'comment' || context === 'any') {
96
+ results.push(new RegExp(`\\/\\*[\\s\\S]*?(${termsPattern})[\\s\\S]*?\\*\\/`, flags));
97
+ }
98
+ if (context === 'string' || context === 'any') {
99
+ results.push(new RegExp(`(['"\`])[^'"\`]*(${termsPattern})[^'"\`]*\\1`, flags));
100
+ }
101
+ return results;
102
+ }
103
+ // Builder: Fallback values
104
+ export function buildFallbackValue(config) {
105
+ const results = [];
106
+ const valuePatterns = config.values.map(valueToPattern);
107
+ const valuesPattern = valuePatterns.join('|');
108
+ for (const op of config.operators) {
109
+ const escapedOp = escapeRegex(op);
110
+ results.push(new RegExp(`${escapedOp}\\s*(${valuesPattern})`, 'g'));
111
+ }
112
+ return results;
113
+ }
114
+ // Builder: Assignment patterns
115
+ export function buildAssignmentPattern(config) {
116
+ const results = [];
117
+ const keysPattern = config.keys.map(escapeRegex).join('|');
118
+ const operators = config.operators || ['=', ':'];
119
+ for (const op of operators) {
120
+ if (config.values && config.values.length > 0) {
121
+ const valuesPattern = config.values.map(escapeRegex).join('|');
122
+ results.push(new RegExp(`(?:${keysPattern})\\s*${escapeRegex(op)}\\s*['"\`]([^'"\`]*(?:${valuesPattern})[^'"\`]*)['"\`]`, 'gi'));
123
+ }
124
+ else {
125
+ results.push(new RegExp(`(?:${keysPattern})\\s*${escapeRegex(op)}\\s*['"\`][^'"\`]{8,}['"\`]`, 'gi'));
126
+ }
127
+ }
128
+ return results;
129
+ }
130
+ // Builder: Chained access
131
+ export function buildChainedAccess(config) {
132
+ const op = config.operator === '?.' ? '\\?\\.' : '\\.';
133
+ const pattern = `(?:${op}\\w+){${config.minDepth},}`;
134
+ return [new RegExp(pattern, 'g')];
135
+ }
136
+ // Builder: Catch handlers
137
+ export function buildCatchHandler(config) {
138
+ const results = [];
139
+ switch (config.behavior) {
140
+ case 'empty':
141
+ results.push(/catch\s*\([^)]*\)\s*\{\s*\}/g);
142
+ break;
143
+ case 'comment_only':
144
+ results.push(/catch\s*\([^)]*\)\s*\{\s*\/\/.*?\s*\}/gs);
145
+ results.push(/catch\s*\([^)]*\)\s*\{\s*\/\*[\s\S]*?\*\/\s*\}/g);
146
+ break;
147
+ case 'log_only':
148
+ results.push(/catch\s*\([^)]*\)\s*\{\s*(?:console\.log|print)\s*\([^)]*\)\s*;?\s*\}/g);
149
+ break;
150
+ case 'returns_value':
151
+ results.push(/catch\s*\([^)]*\)\s*\{[^}]*return\s+(?:null|undefined|false|true|''|""|\[\s*\]|\{\s*\})\s*;?\s*\}/g);
152
+ break;
153
+ case 'ignores_error':
154
+ results.push(/catch\s*\(\s*_\s*\)/g);
155
+ break;
156
+ }
157
+ return results;
158
+ }
159
+ // Builder: Promise catch
160
+ export function buildPromiseCatch(config) {
161
+ const results = [];
162
+ switch (config.behavior) {
163
+ case 'empty':
164
+ results.push(/\.catch\s*\(\s*\(\s*\)\s*=>\s*\{\s*\}\s*\)/g);
165
+ results.push(/\.catch\s*\(\s*\w+\s*=>\s*\{\s*\}\s*\)/g);
166
+ break;
167
+ case 'returns_silent':
168
+ const values = config.silentValues || ['null', 'undefined', 'false', 'true', "''", '""'];
169
+ const valuesPattern = values.map(valueToPattern).join('|');
170
+ results.push(new RegExp(`\\.catch\\s*\\(\\s*\\(\\s*\\)\\s*=>\\s*(?:${valuesPattern})\\s*\\)`, 'g'));
171
+ break;
172
+ case 'ignores_param':
173
+ results.push(/\.catch\s*\(\s*_\s*=>\s*\{?\s*\}?\s*\)/g);
174
+ break;
175
+ }
176
+ return results;
177
+ }
178
+ // Builder: Comment markers
179
+ export function buildCommentMarker(config) {
180
+ const results = [];
181
+ const markersPattern = config.markers.map(escapeRegex).join('|');
182
+ const style = config.style || 'any';
183
+ if (style === 'single' || style === 'any') {
184
+ results.push(new RegExp(`\\/\\/\\s*(${markersPattern})(?::|\\.|\\s).*$`, 'gim'));
185
+ }
186
+ if (style === 'block' || style === 'any') {
187
+ results.push(new RegExp(`\\/\\*[\\s\\S]*?(${markersPattern})[\\s\\S]*?\\*\\/`, 'gi'));
188
+ }
189
+ return results;
190
+ }
191
+ // Builder: String literals
192
+ export function buildStringLiteral(config) {
193
+ const flags = config.caseInsensitive ? 'gi' : 'g';
194
+ const patternsJoined = config.patterns.map(escapeRegex).join('|');
195
+ return [new RegExp(`['"\`](?:[^'"\`]*)?(${patternsJoined})(?:[^'"\`]*)?['"\`]`, flags)];
196
+ }
197
+ // Builder: Secret patterns
198
+ export function buildSecretPattern(config) {
199
+ const results = [];
200
+ switch (config.kind) {
201
+ case 'generic':
202
+ results.push(/(?:api[_-]?key|apikey|secret|password|passwd|pwd|token|auth[_-]?token|access[_-]?token|private[_-]?key)\s*[:=]\s*['"`][^'"`]{8,}['"`]/gi);
203
+ break;
204
+ case 'github':
205
+ results.push(/(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}/g);
206
+ break;
207
+ case 'openai':
208
+ results.push(/sk-[A-Za-z0-9]{48,}/g);
209
+ break;
210
+ case 'aws':
211
+ results.push(/AKIA[0-9A-Z]{16}/g);
212
+ break;
213
+ case 'stripe':
214
+ results.push(/sk_(?:live|test)_[A-Za-z0-9]{24,}/g);
215
+ results.push(/pk_(?:live|test)_[A-Za-z0-9]{24,}/g);
216
+ break;
217
+ case 'custom':
218
+ if (config.customPattern) {
219
+ results.push(new RegExp(config.customPattern, 'g'));
220
+ }
221
+ break;
222
+ }
223
+ return results;
224
+ }
225
+ // Builder: URL patterns
226
+ export function buildUrlPattern(config) {
227
+ const results = [];
228
+ const protocol = config.protocol || 'any';
229
+ if (protocol === 'http' || protocol === 'any') {
230
+ if (config.excludeLocalhost) {
231
+ results.push(/http:\/\/(?!localhost|127\.0\.0\.1)[^\s'"`)]+/g);
232
+ }
233
+ else {
234
+ results.push(/http:\/\/[^\s'"`)]+/g);
235
+ }
236
+ }
237
+ if (protocol === 'https' || protocol === 'any') {
238
+ results.push(/https:\/\/[^\s'"`)]+/g);
239
+ }
240
+ return results;
241
+ }
242
+ // Builder: Suppression comments
243
+ export function buildSuppressionComment(config) {
244
+ const toolsPattern = config.tools.map(escapeRegex).join('|');
245
+ return [new RegExp(`\\/\\/\\s*(?:@)?(${toolsPattern})`, 'g')];
246
+ }
247
+ // Builder: Type casts
248
+ export function buildTypeCast(config) {
249
+ const targetsPattern = config.targets.map(escapeRegex).join('|');
250
+ return [new RegExp(`as\\s+(${targetsPattern})(?!\\w)`, 'g')];
251
+ }
252
+ // Builder: Comparison
253
+ export function buildComparison(config) {
254
+ const results = [];
255
+ for (const op of config.operators) {
256
+ if (op === '==') {
257
+ results.push(/[^!=]==[^=]/g);
258
+ }
259
+ else if (op === '!=') {
260
+ results.push(/!=[^=]/g);
261
+ }
262
+ else if (op === '===') {
263
+ results.push(/===/g);
264
+ }
265
+ else if (op === '!==') {
266
+ results.push(/!==/g);
267
+ }
268
+ }
269
+ return results;
270
+ }
271
+ // Builder: Loop patterns
272
+ export function buildLoopPattern(config) {
273
+ const results = [];
274
+ switch (config.kind) {
275
+ case 'for_in':
276
+ results.push(/for\s*\(\s*(?:var|let|const)\s+\w+\s+in\s+/g);
277
+ break;
278
+ case 'while_true':
279
+ results.push(/while\s*\(\s*true\s*\)/g);
280
+ break;
281
+ case 'infinite':
282
+ results.push(/while\s*\(\s*true\s*\)/g);
283
+ results.push(/for\s*\(\s*;\s*;\s*\)/g);
284
+ break;
285
+ }
286
+ return results;
287
+ }
288
+ // Builder: Raw regex (escape hatch)
289
+ export function buildRawRegex(config) {
290
+ return [new RegExp(config.pattern, config.flags || 'g')];
291
+ }
292
+ // Main dispatcher: convert any MatchConfig to RegExp[]
293
+ export function buildPattern(config) {
294
+ switch (config.type) {
295
+ case 'empty_block':
296
+ return buildEmptyBlock(config);
297
+ case 'function_call':
298
+ return buildFunctionCall(config);
299
+ case 'returns_only':
300
+ return buildReturnsOnly(config);
301
+ case 'contains_text':
302
+ return buildContainsText(config);
303
+ case 'fallback_value':
304
+ return buildFallbackValue(config);
305
+ case 'assignment_pattern':
306
+ return buildAssignmentPattern(config);
307
+ case 'chained_access':
308
+ return buildChainedAccess(config);
309
+ case 'catch_handler':
310
+ return buildCatchHandler(config);
311
+ case 'promise_catch':
312
+ return buildPromiseCatch(config);
313
+ case 'comment_marker':
314
+ return buildCommentMarker(config);
315
+ case 'string_literal':
316
+ return buildStringLiteral(config);
317
+ case 'secret_pattern':
318
+ return buildSecretPattern(config);
319
+ case 'url_pattern':
320
+ return buildUrlPattern(config);
321
+ case 'suppression_comment':
322
+ return buildSuppressionComment(config);
323
+ case 'type_cast':
324
+ return buildTypeCast(config);
325
+ case 'comparison':
326
+ return buildComparison(config);
327
+ case 'loop_pattern':
328
+ return buildLoopPattern(config);
329
+ case 'raw_regex':
330
+ return buildRawRegex(config);
331
+ default:
332
+ console.warn(`Unknown match type: ${config.type}`);
333
+ return [];
334
+ }
335
+ }
@@ -0,0 +1,16 @@
1
+ import { PatternDefinition, CompiledPattern } from './types.js';
2
+ import { Category } from '../types.js';
3
+ export declare function compileDefinition(definition: PatternDefinition): CompiledPattern;
4
+ export declare function compileCategory(definitions: PatternDefinition[]): CompiledPattern[];
5
+ export declare function getCompiledPatterns(category?: Category): CompiledPattern[];
6
+ export declare function clearPatternCache(): void;
7
+ export declare function getDefinitions(category?: Category): PatternDefinition[];
8
+ export declare function validateDefinitions(): {
9
+ valid: boolean;
10
+ errors: string[];
11
+ };
12
+ export declare function getPatternStats(): {
13
+ total: number;
14
+ byCategory: Record<string, number>;
15
+ bySeverity: Record<string, number>;
16
+ };
@@ -0,0 +1,108 @@
1
+ // Pattern compiler - transforms definitions into executable patterns
2
+ import { buildPattern } from './builders.js';
3
+ import { securityDefinitions, deceptiveDefinitions, placeholderDefinitions, errorDefinitions, } from './definitions/index.js';
4
+ // Cache for compiled patterns
5
+ let compiledCache = null;
6
+ // Compile a single pattern definition
7
+ export function compileDefinition(definition) {
8
+ const patterns = buildPattern(definition.match);
9
+ return {
10
+ id: definition.id,
11
+ title: definition.title,
12
+ description: definition.description,
13
+ severity: definition.severity,
14
+ category: definition.category,
15
+ suggestion: definition.suggestion,
16
+ patterns,
17
+ verification: definition.verification,
18
+ };
19
+ }
20
+ // Compile all definitions of a specific category
21
+ export function compileCategory(definitions) {
22
+ return definitions.map(compileDefinition);
23
+ }
24
+ // Get all compiled patterns, optionally filtered by category
25
+ export function getCompiledPatterns(category) {
26
+ // Build cache if needed
27
+ if (!compiledCache) {
28
+ compiledCache = new Map();
29
+ compiledCache.set('security', compileCategory(securityDefinitions));
30
+ compiledCache.set('deceptive', compileCategory(deceptiveDefinitions));
31
+ compiledCache.set('placeholder', compileCategory(placeholderDefinitions));
32
+ compiledCache.set('error', compileCategory(errorDefinitions));
33
+ }
34
+ if (category) {
35
+ return compiledCache.get(category) || [];
36
+ }
37
+ // Return all patterns
38
+ const all = [];
39
+ for (const patterns of compiledCache.values()) {
40
+ all.push(...patterns);
41
+ }
42
+ return all;
43
+ }
44
+ // Clear the cache (useful for testing or hot reload)
45
+ export function clearPatternCache() {
46
+ compiledCache = null;
47
+ }
48
+ // Get raw definitions (for inspection/debugging)
49
+ export function getDefinitions(category) {
50
+ const allDefs = {
51
+ security: securityDefinitions,
52
+ deceptive: deceptiveDefinitions,
53
+ placeholder: placeholderDefinitions,
54
+ error: errorDefinitions,
55
+ strength: [], // Strengths use a different system
56
+ };
57
+ if (category) {
58
+ return allDefs[category] || [];
59
+ }
60
+ return [
61
+ ...securityDefinitions,
62
+ ...deceptiveDefinitions,
63
+ ...placeholderDefinitions,
64
+ ...errorDefinitions,
65
+ ];
66
+ }
67
+ // Validate all definitions (for testing)
68
+ export function validateDefinitions() {
69
+ const errors = [];
70
+ const allDefs = getDefinitions();
71
+ const ids = new Set();
72
+ for (const def of allDefs) {
73
+ // Check for duplicate IDs
74
+ if (ids.has(def.id)) {
75
+ errors.push(`Duplicate pattern ID: ${def.id}`);
76
+ }
77
+ ids.add(def.id);
78
+ // Try to compile and check for errors
79
+ try {
80
+ const compiled = compileDefinition(def);
81
+ if (compiled.patterns.length === 0) {
82
+ errors.push(`Pattern ${def.id} compiled to zero regex patterns`);
83
+ }
84
+ }
85
+ catch (e) {
86
+ errors.push(`Pattern ${def.id} failed to compile: ${e instanceof Error ? e.message : String(e)}`);
87
+ }
88
+ }
89
+ return {
90
+ valid: errors.length === 0,
91
+ errors,
92
+ };
93
+ }
94
+ // Stats about patterns
95
+ export function getPatternStats() {
96
+ const allDefs = getDefinitions();
97
+ const byCategory = {};
98
+ const bySeverity = {};
99
+ for (const def of allDefs) {
100
+ byCategory[def.category] = (byCategory[def.category] || 0) + 1;
101
+ bySeverity[def.severity] = (bySeverity[def.severity] || 0) + 1;
102
+ }
103
+ return {
104
+ total: allDefs.length,
105
+ byCategory,
106
+ bySeverity,
107
+ };
108
+ }
@@ -0,0 +1,2 @@
1
+ import { PatternDefinition } from '../types.js';
2
+ export declare const deceptiveDefinitions: PatternDefinition[];