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.
package/README.md CHANGED
@@ -12,6 +12,75 @@ AI coding assistants can inadvertently introduce subtle issues: hardcoded secret
12
12
  - **Balanced analysis**: Detects both issues AND strengths for fair code assessment
13
13
  - **Multi-language support**: Works with TypeScript, JavaScript, Python, Go, Rust, Java, and more
14
14
 
15
+ ## Why Not Tree-sitter or AST-Based Tools?
16
+
17
+ CodeSentinel intentionally uses a **pattern-based approach** rather than AST parsing. Here's why:
18
+
19
+ ### The Problem We Solve Is Different
20
+
21
+ Traditional linters (ESLint, tree-sitter) detect **syntax errors** and **style violations**. CodeSentinel detects **semantically deceptive patterns** - code that is:
22
+
23
+ - Syntactically valid (passes all linters)
24
+ - Structurally correct (valid AST)
25
+ - **But hides serious issues** that AI agents commonly produce
26
+
27
+ ### Examples AST Tools Miss
28
+
29
+ ```javascript
30
+ // AST sees: valid try-catch block
31
+ // CodeSentinel sees: error swallowing that masks failures
32
+ try { riskyOperation(); } catch(e) { }
33
+
34
+ // AST sees: valid function returning boolean
35
+ // CodeSentinel sees: fake implementation that always succeeds
36
+ function validateUser() { return true; } // TODO: implement
37
+
38
+ // AST sees: valid fallback expression
39
+ // CodeSentinel sees: failure masking - "no data" vs "fetch failed" indistinguishable
40
+ const users = response.data || [];
41
+
42
+ // AST sees: valid return statement
43
+ // CodeSentinel sees: silent failure hiding
44
+ if (error) { return null; } // error case
45
+ ```
46
+
47
+ ### What Each Approach Detects
48
+
49
+ | Issue Type | AST/Tree-sitter | CodeSentinel |
50
+ |:-----------|:----------------|:-------------|
51
+ | Syntax errors | Yes | No (not our goal) |
52
+ | Missing semicolons | Yes | No |
53
+ | Unused variables | Yes | No |
54
+ | **Empty catch blocks** | Partially | Yes |
55
+ | **Silent error returns** | No | Yes |
56
+ | **Fake success responses** | No | Yes |
57
+ | **TODO/placeholder code** | No | Yes |
58
+ | **Error-masking fallbacks** | No | Yes |
59
+ | **Hardcoded secrets** | Limited | Yes |
60
+ | **Deceptive comments** | No | Yes |
61
+
62
+ ### The Real Issue: Agent Behavior
63
+
64
+ AI coding agents produce code that **looks correct** but contains subtle deceptions:
65
+
66
+ 1. **"Making the error go away"** - Empty catches, silent returns, swallowed exceptions
67
+ 2. **Placeholder implementations** - `return true`, `return []`, TODO comments
68
+ 3. **False confidence patterns** - `|| []` fallbacks that mask fetch failures
69
+ 4. **Suppression abuse** - `@ts-ignore`, `eslint-disable` to hide type errors
70
+
71
+ These patterns pass every linter and compile successfully. AST tools see valid structure. Only pattern-based detection catches the **semantic intent** behind the code.
72
+
73
+ ### When to Use What
74
+
75
+ | Tool | Use For |
76
+ |:-----|:--------|
77
+ | ESLint/TSLint | Style consistency, syntax rules, unused code |
78
+ | Tree-sitter | Syntax highlighting, code navigation, refactoring |
79
+ | TypeScript | Type safety, compile-time errors |
80
+ | **CodeSentinel** | Agent-generated deceptions, error hiding, incomplete implementations |
81
+
82
+ CodeSentinel complements these tools - it catches what they structurally cannot.
83
+
15
84
  ## Features
16
85
 
17
86
  - **Security Analysis** (16 patterns): Hardcoded secrets, SQL injection, XSS, command injection, insecure crypto, disabled SSL, and more
@@ -286,32 +355,105 @@ CodeSentinel detects language from file extensions:
286
355
 
287
356
  ## Extending CodeSentinel
288
357
 
289
- Add custom patterns by editing files in `src/analyzers/`:
358
+ CodeSentinel uses a **data-driven pattern system** that separates pattern definitions from regex generation. This makes adding new patterns easier and more maintainable.
290
359
 
360
+ ### Project Structure
361
+
362
+ ```
363
+ src/
364
+ ├── patterns/
365
+ │ ├── types.ts # Type definitions for pattern configs
366
+ │ ├── builders.ts # Functions that generate regex from configs
367
+ │ ├── compiler.ts # Compiles definitions to executable patterns
368
+ │ └── definitions/
369
+ │ ├── security.ts # Security vulnerability patterns
370
+ │ ├── deceptive.ts # Error-hiding patterns
371
+ │ ├── placeholders.ts # Incomplete code patterns
372
+ │ ├── errors.ts # Code smell patterns
373
+ │ └── index.ts # Exports all definitions
374
+ ├── analyzers/
375
+ │ ├── core.ts # Unified analyzer using compiled patterns
376
+ │ ├── security.ts # Security analyzer (delegates to core)
377
+ │ ├── deceptive.ts # Deceptive analyzer (delegates to core)
378
+ │ ├── placeholders.ts # Placeholder analyzer (delegates to core)
379
+ │ ├── errors.ts # Error analyzer (delegates to core)
380
+ │ └── strengths.ts # Strength analyzer
381
+ └── index.ts # MCP server entry point
291
382
  ```
292
- src/analyzers/
293
- ├── security.ts # Security vulnerability patterns
294
- ├── deceptive.ts # Error-hiding patterns
295
- ├── placeholders.ts # Incomplete code patterns
296
- ├── errors.ts # Code smell patterns
297
- └── strengths.ts # Good practice patterns
383
+
384
+ ### Adding a New Pattern
385
+
386
+ Instead of writing regex manually, you define **what** to detect and the system generates the regex:
387
+
388
+ ```typescript
389
+ // Old approach (manual regex)
390
+ {
391
+ id: 'CS-DEC001',
392
+ pattern: /catch\s*\([^)]*\)\s*\{\s*\}/g, // Error-prone
393
+ title: 'Empty Catch Block',
394
+ // ...
395
+ }
396
+
397
+ // New approach (data-driven)
398
+ {
399
+ id: 'CS-DEC001',
400
+ title: 'Empty Catch Block',
401
+ description: 'Silently swallowing errors makes debugging impossible.',
402
+ severity: 'high',
403
+ category: 'deceptive',
404
+ suggestion: 'At minimum, log the error. Better: handle it appropriately.',
405
+ match: {
406
+ type: 'catch_handler',
407
+ behavior: 'empty'
408
+ }
409
+ }
298
410
  ```
299
411
 
300
- Each pattern follows this structure:
412
+ ### Available Match Types
413
+
414
+ | Match Type | Description | Example Config |
415
+ |:-----------|:------------|:---------------|
416
+ | `empty_block` | Empty catch/finally/promise blocks | `{ type: 'empty_block', constructs: ['catch', '.catch'] }` |
417
+ | `function_call` | Function/method calls | `{ type: 'function_call', names: ['eval', 'exec'] }` |
418
+ | `returns_only` | Return statements with specific values | `{ type: 'returns_only', values: ['null', '[]', '{}'] }` |
419
+ | `contains_text` | Text in comments/strings | `{ type: 'contains_text', terms: ['TODO', 'FIXME'], context: 'comment' }` |
420
+ | `fallback_value` | Fallback patterns | `{ type: 'fallback_value', operators: ['\|\|'], values: ['[]'] }` |
421
+ | `catch_handler` | Catch block behaviors | `{ type: 'catch_handler', behavior: 'empty' }` |
422
+ | `promise_catch` | Promise .catch() behaviors | `{ type: 'promise_catch', behavior: 'returns_silent' }` |
423
+ | `comment_marker` | TODO/FIXME/HACK markers | `{ type: 'comment_marker', markers: ['TODO', 'FIXME'] }` |
424
+ | `string_literal` | Patterns inside strings | `{ type: 'string_literal', patterns: ['password', 'secret'] }` |
425
+ | `secret_pattern` | API keys and tokens | `{ type: 'secret_pattern', kind: 'github' }` |
426
+ | `url_pattern` | URL patterns | `{ type: 'url_pattern', protocol: 'http', excludeLocalhost: true }` |
427
+ | `suppression_comment` | Linter suppressions | `{ type: 'suppression_comment', tools: ['ts-ignore', 'eslint-disable'] }` |
428
+ | `type_cast` | Type casts | `{ type: 'type_cast', targets: ['any'] }` |
429
+ | `comparison` | Comparison operators | `{ type: 'comparison', operators: ['==', '!='] }` |
430
+ | `loop_pattern` | Loop patterns | `{ type: 'loop_pattern', kind: 'while_true' }` |
431
+ | `raw_regex` | Escape hatch for complex patterns | `{ type: 'raw_regex', pattern: 'your-regex', flags: 'gi' }` |
432
+
433
+ ### Step-by-Step: Adding a Pattern
434
+
435
+ 1. **Choose the category** - security, deceptive, placeholder, or error
436
+ 2. **Open the definition file** - `src/patterns/definitions/<category>.ts`
437
+ 3. **Add a new pattern definition** using the appropriate match type
438
+ 4. **Build** - `npm run build`
439
+ 5. **Test** - Use the MCP inspector to verify detection
440
+
441
+ ### Pattern Definition Structure
301
442
 
302
443
  ```typescript
303
444
  {
304
- id: 'CS-SEC001', // Unique ID with category prefix
305
- pattern: /regex/g, // RegExp to match
306
- title: 'Short description',
307
- description: 'Detailed explanation',
308
- severity: 'critical', // critical | high | medium | low | info
309
- category: 'security',
310
- suggestion: 'How to fix',
311
- verification: { // Optional: reduce false positives
312
- assumption: 'What we assume is true',
313
- confirmIf: 'When to confirm as real issue',
314
- falsePositiveIf: 'When to dismiss'
445
+ id: string; // Unique ID: CS-<CAT><NUM> (e.g., CS-SEC001)
446
+ title: string; // Short description (displayed in results)
447
+ description: string; // Detailed explanation of the issue
448
+ severity: Severity; // 'critical' | 'high' | 'medium' | 'low' | 'info'
449
+ category: Category; // 'security' | 'deceptive' | 'placeholder' | 'error'
450
+ suggestion?: string; // How to fix the issue
451
+ match: MatchConfig; // What to detect (see match types above)
452
+ verification?: { // Optional: reduce false positives
453
+ status: 'needs_verification' | 'confirmed';
454
+ assumption?: string;
455
+ confirmIf?: string;
456
+ falsePositiveIf?: string;
315
457
  }
316
458
  }
317
459
  ```
@@ -0,0 +1,7 @@
1
+ import { Issue, Category } from '../types.js';
2
+ export declare function analyzeWithPatterns(code: string, filename: string, category: Category): Issue[];
3
+ export declare function analyzeSecurityWithPatterns(code: string, filename: string): Issue[];
4
+ export declare function analyzeDeceptiveWithPatterns(code: string, filename: string): Issue[];
5
+ export declare function analyzePlaceholdersWithPatterns(code: string, filename: string): Issue[];
6
+ export declare function analyzeErrorsWithPatterns(code: string, filename: string): Issue[];
7
+ export declare function analyzeAllWithPatterns(code: string, filename: string): Issue[];
@@ -0,0 +1,69 @@
1
+ // Core analyzer - uses compiled patterns from the data-driven system
2
+ import { getCompiledPatterns } from '../patterns/index.js';
3
+ // Analyze code using compiled patterns
4
+ export function analyzeWithPatterns(code, filename, category) {
5
+ const issues = [];
6
+ const lines = code.split('\n');
7
+ const compiledPatterns = getCompiledPatterns(category);
8
+ for (const pattern of compiledPatterns) {
9
+ for (const regex of pattern.patterns) {
10
+ // Reset regex state for global patterns
11
+ regex.lastIndex = 0;
12
+ let match;
13
+ while ((match = regex.exec(code)) !== null) {
14
+ // Calculate line number
15
+ const beforeMatch = code.substring(0, match.index);
16
+ const lineNumber = beforeMatch.split('\n').length;
17
+ const lineContent = lines[lineNumber - 1] || '';
18
+ const matchedValue = match[0];
19
+ // Build verification with actual values substituted
20
+ let verification = pattern.verification;
21
+ if (verification) {
22
+ verification = {
23
+ ...verification,
24
+ commands: verification.commands?.map(cmd => cmd
25
+ .replace(/<matched_value>/g, matchedValue.substring(0, 20) + '...')
26
+ .replace(/<filename>/g, filename))
27
+ };
28
+ }
29
+ issues.push({
30
+ id: pattern.id,
31
+ category: pattern.category,
32
+ severity: pattern.severity,
33
+ title: pattern.title,
34
+ description: pattern.description,
35
+ line: lineNumber,
36
+ code: lineContent.trim(),
37
+ suggestion: pattern.suggestion,
38
+ verification
39
+ });
40
+ // Prevent infinite loops for patterns without global flag
41
+ if (!regex.global)
42
+ break;
43
+ }
44
+ }
45
+ }
46
+ return issues;
47
+ }
48
+ // Convenience functions for each category
49
+ export function analyzeSecurityWithPatterns(code, filename) {
50
+ return analyzeWithPatterns(code, filename, 'security');
51
+ }
52
+ export function analyzeDeceptiveWithPatterns(code, filename) {
53
+ return analyzeWithPatterns(code, filename, 'deceptive');
54
+ }
55
+ export function analyzePlaceholdersWithPatterns(code, filename) {
56
+ return analyzeWithPatterns(code, filename, 'placeholder');
57
+ }
58
+ export function analyzeErrorsWithPatterns(code, filename) {
59
+ return analyzeWithPatterns(code, filename, 'error');
60
+ }
61
+ // Analyze all categories at once
62
+ export function analyzeAllWithPatterns(code, filename) {
63
+ return [
64
+ ...analyzeSecurityWithPatterns(code, filename),
65
+ ...analyzeDeceptiveWithPatterns(code, filename),
66
+ ...analyzePlaceholdersWithPatterns(code, filename),
67
+ ...analyzeErrorsWithPatterns(code, filename),
68
+ ];
69
+ }
@@ -1,200 +1,7 @@
1
- // These patterns detect code that hides errors or creates false confidence
2
- const deceptivePatterns = [
3
- // Empty catch blocks
4
- {
5
- id: 'CS-DEC001',
6
- pattern: /catch\s*\([^)]*\)\s*\{\s*\}/g,
7
- title: 'Empty Catch Block',
8
- description: 'Silently swallowing errors makes debugging impossible.',
9
- severity: 'high',
10
- category: 'deceptive',
11
- suggestion: 'At minimum, log the error. Better: handle it appropriately or rethrow.'
12
- },
13
- {
14
- id: 'CS-DEC002',
15
- pattern: /catch\s*\([^)]*\)\s*\{\s*\/\/.*?\s*\}/gs,
16
- title: 'Catch Block with Only Comments',
17
- description: 'A catch block with only comments still swallows errors.',
18
- severity: 'high',
19
- category: 'deceptive',
20
- suggestion: 'Add actual error handling, not just comments.'
21
- },
22
- {
23
- id: 'CS-DEC003',
24
- pattern: /catch\s*\([^)]*\)\s*\{\s*(?:console\.log|print)\s*\([^)]*\)\s*;?\s*\}/g,
25
- title: 'Catch with Only Console.log',
26
- description: 'Logging alone does not handle the error - execution continues as if nothing happened.',
27
- severity: 'medium',
28
- category: 'deceptive',
29
- suggestion: 'Decide: should execution continue? Add recovery logic or rethrow.'
30
- },
31
- // Silent promise rejections
32
- {
33
- id: 'CS-DEC010',
34
- pattern: /\.catch\s*\(\s*\(\s*\)\s*=>\s*\{\s*\}\s*\)/g,
35
- title: 'Empty Promise Catch',
36
- description: 'Silently ignoring promise rejections hides async errors.',
37
- severity: 'high',
38
- category: 'deceptive',
39
- suggestion: 'Handle the rejection or let it propagate for proper error handling.'
40
- },
41
- {
42
- id: 'CS-DEC011',
43
- pattern: /\.catch\s*\(\s*\(\s*\)\s*=>\s*(?:null|undefined|false|true|''|"")\s*\)/g,
44
- title: 'Promise Catch Returns Silent Value',
45
- description: 'Returning a value from catch masks the error - callers won\'t know something failed.',
46
- severity: 'high',
47
- category: 'deceptive',
48
- suggestion: 'Return a distinguishable error state or rethrow.'
49
- },
50
- {
51
- id: 'CS-DEC012',
52
- pattern: /\.catch\s*\(\s*_\s*=>\s*\{?\s*\}?\s*\)/g,
53
- title: 'Catch with Ignored Error Parameter',
54
- description: 'Using _ for error parameter signals intentional ignore - but is it really safe to ignore?',
55
- severity: 'medium',
56
- category: 'deceptive',
57
- suggestion: 'Document why ignoring this error is safe, or handle it.'
58
- },
59
- // Fallback values that mask failures
60
- {
61
- id: 'CS-DEC020',
62
- pattern: /\|\|\s*\[\s*\]/g,
63
- title: 'Empty Array Fallback',
64
- description: 'Falling back to [] can mask failed data fetching - code continues as if data was empty.',
65
- severity: 'medium',
66
- category: 'deceptive',
67
- suggestion: 'Distinguish between "no data" and "failed to fetch". Consider throwing or returning null.'
68
- },
69
- {
70
- id: 'CS-DEC021',
71
- pattern: /\|\|\s*\{\s*\}/g,
72
- title: 'Empty Object Fallback',
73
- description: 'Falling back to {} can hide parsing or fetching failures.',
74
- severity: 'medium',
75
- category: 'deceptive',
76
- suggestion: 'Handle the undefined/null case explicitly rather than masking it.'
77
- },
78
- {
79
- id: 'CS-DEC022',
80
- pattern: /\?\?\s*(?:\[\s*\]|\{\s*\}|''|"")/g,
81
- title: 'Nullish Coalescing to Empty Value',
82
- description: 'Defaulting to empty values with ?? can mask null responses that indicate errors.',
83
- severity: 'low',
84
- category: 'deceptive',
85
- suggestion: 'Verify that null/undefined truly means "use default" vs "something went wrong".'
86
- },
87
- // Optional chaining abuse
88
- {
89
- id: 'CS-DEC030',
90
- pattern: /\?\.\w+\?\.\w+\?\.\w+\?\.\w+/g,
91
- title: 'Excessive Optional Chaining',
92
- description: 'Deep optional chaining (4+ levels) often masks structural problems or missing validation.',
93
- severity: 'medium',
94
- category: 'deceptive',
95
- suggestion: 'Validate data shape upfront rather than optional-chaining through uncertain structures.'
96
- },
97
- // Error-hiding returns
98
- {
99
- id: 'CS-DEC040',
100
- pattern: /return\s+(?:null|undefined|false)\s*;?\s*\/\/\s*(?:error|fail|todo|fixme)/gi,
101
- title: 'Silent Error Return',
102
- description: 'Returning null/false on error with a comment - callers may not check for this.',
103
- severity: 'high',
104
- category: 'deceptive',
105
- suggestion: 'Throw an error or return a Result/Either type that forces handling.'
106
- },
107
- {
108
- id: 'CS-DEC041',
109
- pattern: /if\s*\([^)]*error[^)]*\)\s*\{\s*return\s*;?\s*\}/gi,
110
- title: 'Silent Return on Error',
111
- description: 'Returning silently when an error is detected - no logging, no propagation.',
112
- severity: 'high',
113
- category: 'deceptive',
114
- suggestion: 'Log the error or throw it. Silent returns make debugging a nightmare.'
115
- },
116
- // Timeout-based "fixes"
117
- {
118
- id: 'CS-DEC050',
119
- pattern: /setTimeout\s*\([^,]+,\s*\d+\s*\)\s*;?\s*\/\/.*?(?:fix|hack|workaround|retry)/gi,
120
- title: 'Timeout as Error Workaround',
121
- description: 'Using setTimeout to "fix" timing issues often masks race conditions.',
122
- severity: 'medium',
123
- category: 'deceptive',
124
- suggestion: 'Fix the underlying race condition. Use proper async coordination.'
125
- },
126
- // Suppressed warnings/errors
127
- {
128
- id: 'CS-DEC060',
129
- pattern: /\/\/\s*(?:@ts-ignore|@ts-expect-error|eslint-disable|noqa)/g,
130
- title: 'Linter/Type Check Suppression',
131
- description: 'Suppressing type errors or linter warnings may hide real issues.',
132
- severity: 'low',
133
- category: 'deceptive',
134
- suggestion: 'Fix the underlying issue. If suppression is needed, document why.'
135
- },
136
- {
137
- id: 'CS-DEC061',
138
- pattern: /as\s+any(?!\w)/g,
139
- title: 'TypeScript "as any" Cast',
140
- description: 'Casting to any defeats TypeScript\'s type safety.',
141
- severity: 'medium',
142
- category: 'deceptive',
143
- suggestion: 'Use proper type definitions or unknown with type guards.'
144
- },
145
- // Fake success responses
146
- {
147
- id: 'CS-DEC070',
148
- pattern: /return\s*\{\s*(?:success|ok|status)\s*:\s*true[^}]*\}\s*;?\s*\/\/.*?(?:todo|fixme|hack)/gi,
149
- title: 'Fake Success Response',
150
- description: 'Returning success without actually doing the work.',
151
- severity: 'critical',
152
- category: 'deceptive',
153
- suggestion: 'Implement the actual functionality or return an honest error.'
154
- },
155
- // Console.error without throwing
156
- {
157
- id: 'CS-DEC080',
158
- pattern: /console\.error\s*\([^)]+\)\s*;?\s*(?!\s*throw)/g,
159
- title: 'console.error Without Throw',
160
- description: 'Logging an error but continuing execution - the error may not be handled.',
161
- severity: 'low',
162
- category: 'deceptive',
163
- suggestion: 'Consider if execution should continue. If not, throw after logging.'
164
- }
165
- ];
1
+ // Deceptive pattern analyzer - uses data-driven pattern system
2
+ // This file now delegates to the core analyzer with compiled patterns
3
+ import { analyzeDeceptiveWithPatterns } from './core.js';
4
+ // Main export - uses the data-driven pattern system
166
5
  export function analyzeDeceptivePatterns(code, filename) {
167
- const issues = [];
168
- const lines = code.split('\n');
169
- for (const patternDef of deceptivePatterns) {
170
- patternDef.pattern.lastIndex = 0;
171
- let match;
172
- while ((match = patternDef.pattern.exec(code)) !== null) {
173
- const beforeMatch = code.substring(0, match.index);
174
- const lineNumber = beforeMatch.split('\n').length;
175
- const lineContent = lines[lineNumber - 1] || '';
176
- // Build verification with actual values substituted
177
- let verification = patternDef.verification;
178
- if (verification) {
179
- verification = {
180
- ...verification,
181
- commands: verification.commands?.map(cmd => cmd.replace(/<filename>/g, filename))
182
- };
183
- }
184
- issues.push({
185
- id: patternDef.id,
186
- category: patternDef.category,
187
- severity: patternDef.severity,
188
- title: patternDef.title,
189
- description: patternDef.description,
190
- line: lineNumber,
191
- code: lineContent.trim(),
192
- suggestion: patternDef.suggestion,
193
- verification
194
- });
195
- if (!patternDef.pattern.global)
196
- break;
197
- }
198
- }
199
- return issues;
6
+ return analyzeDeceptiveWithPatterns(code, filename);
200
7
  }