code-sentinel-mcp 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/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # CodeSentinel MCP Server
2
+
3
+ A code quality analysis MCP server that detects security issues, deceptive patterns, placeholders, and highlights code strengths.
4
+
5
+ ## Features
6
+
7
+ - 🔒 **Security Analysis**: Hardcoded secrets, SQL injection, XSS, insecure crypto, and more
8
+ - 🎭 **Deceptive Pattern Detection**: Empty catch blocks, silent failures, error-hiding returns
9
+ - 📝 **Placeholder Detection**: TODO/FIXME, lorem ipsum, test data, incomplete implementations
10
+ - ⚠️ **Error & Code Smell Detection**: Type coercion, null references, async issues
11
+ - 💪 **Strength Recognition**: Highlights good practices like typing, error handling, tests
12
+ - 📊 **HTML Reports**: Beautiful visual reports with scores and suggestions
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ # Clone or copy the project
18
+ cd code-sentinel
19
+
20
+ # Install dependencies
21
+ npm install
22
+
23
+ # Build
24
+ npm run build
25
+ ```
26
+
27
+ ## Usage with Claude Code
28
+
29
+ Add to your Claude Code MCP configuration:
30
+
31
+ ```bash
32
+ claude mcp add code-sentinel -- node /path/to/code-sentinel/build/index.js
33
+ ```
34
+
35
+ Or manually add to your config:
36
+
37
+ ```json
38
+ {
39
+ "mcpServers": {
40
+ "code-sentinel": {
41
+ "command": "node",
42
+ "args": ["/path/to/code-sentinel/build/index.js"]
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ ## Available Tools
49
+
50
+ ### `analyze_code`
51
+ Full analysis returning structured JSON with all issues and strengths.
52
+
53
+ ### `generate_report`
54
+ Full analysis with HTML report output.
55
+
56
+ ### `check_security`
57
+ Security-focused analysis only.
58
+
59
+ ### `check_deceptive_patterns`
60
+ Check for error-hiding and deceptive code patterns.
61
+
62
+ ### `check_placeholders`
63
+ Find TODOs, dummy data, and incomplete code.
64
+
65
+ ## Example Usage in Claude Code
66
+
67
+ ```
68
+ Analyze this code for quality issues:
69
+
70
+ const API_KEY = "sk-abc123456789";
71
+
72
+ async function fetchData() {
73
+ try {
74
+ const response = await fetch(url);
75
+ return response.json();
76
+ } catch (e) {
77
+ // TODO: handle error
78
+ }
79
+ }
80
+ ```
81
+
82
+ Claude will automatically use CodeSentinel to analyze and report:
83
+ - Critical: Hardcoded API key detected
84
+ - High: Empty catch block (deceptive pattern)
85
+ - Low: TODO comment found
86
+
87
+ ## Detection Categories
88
+
89
+ ### Security Issues (SEC)
90
+ - Hardcoded secrets (API keys, tokens, passwords)
91
+ - SQL injection patterns
92
+ - XSS vulnerabilities
93
+ - Insecure random, weak crypto
94
+ - Disabled SSL validation
95
+ - Eval and dynamic code execution
96
+
97
+ ### Deceptive Patterns (DEC)
98
+ - Empty catch blocks
99
+ - Silent promise rejections
100
+ - Error-hiding fallbacks (|| [], || {})
101
+ - Fake success responses
102
+ - Excessive optional chaining
103
+ - Linter suppression comments
104
+
105
+ ### Placeholders (PH)
106
+ - TODO/FIXME/HACK/XXX comments
107
+ - Lorem ipsum text
108
+ - Test emails and passwords
109
+ - Debug console.log
110
+ - Debugger statements
111
+ - Not implemented errors
112
+
113
+ ### Errors & Smells (ERR)
114
+ - Loose equality (==)
115
+ - Assignment in conditions
116
+ - parseInt without radix
117
+ - Array mutation during iteration
118
+ - Floating point comparison
119
+ - Await in constructor
120
+
121
+ ## Scoring
122
+
123
+ The quality score (0-100) is calculated based on:
124
+ - Critical issues: -25 points each
125
+ - High issues: -15 points each
126
+ - Medium issues: -5 points each
127
+ - Low issues: -1 point each
128
+ - Strengths: +2 points each
129
+
130
+ ## Extending
131
+
132
+ Add new patterns by editing the analyzer files in `src/analyzers/`:
133
+ - `security.ts` - Security vulnerability patterns
134
+ - `deceptive.ts` - Error-hiding patterns
135
+ - `placeholders.ts` - Incomplete code patterns
136
+ - `errors.ts` - Code smell patterns
137
+ - `strengths.ts` - Good practice patterns
138
+
139
+ Each pattern includes:
140
+ - `id`: Unique identifier (e.g., SEC001)
141
+ - `pattern`: RegExp to match
142
+ - `title`: Short description
143
+ - `description`: Detailed explanation
144
+ - `severity`: critical | high | medium | low
145
+ - `suggestion`: How to fix (optional)
146
+
147
+ ## License
148
+
149
+ MIT
@@ -0,0 +1,2 @@
1
+ import { Issue } from '../types.js';
2
+ export declare function analyzeDeceptivePatterns(code: string, filename: string): Issue[];
@@ -0,0 +1,200 @@
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
+ ];
166
+ 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;
200
+ }
@@ -0,0 +1,2 @@
1
+ import { Issue } from '../types.js';
2
+ export declare function analyzeErrors(code: string, filename: string): Issue[];
@@ -0,0 +1,214 @@
1
+ const errorPatterns = [
2
+ // Unhandled promises
3
+ {
4
+ id: 'CS-ERR001',
5
+ pattern: /(?:^|\s)(?:await\s+)?(?:\w+\.)+\w+\s*\([^)]*\)\s*;?\s*$/gm,
6
+ title: 'Potentially Unhandled Promise',
7
+ description: 'Async call without await or .then()/.catch() may silently fail.',
8
+ severity: 'medium',
9
+ category: 'error',
10
+ suggestion: 'Use await with try/catch or add .catch() handler.'
11
+ },
12
+ // Missing error handling
13
+ {
14
+ id: 'CS-ERR010',
15
+ pattern: /async\s+(?:function\s+\w+|\w+\s*=\s*async)\s*\([^)]*\)\s*(?::\s*\w+\s*)?\{(?:(?!try\s*\{).)*\}/gs,
16
+ title: 'Async Function Without Try/Catch',
17
+ description: 'Async function has no error handling.',
18
+ severity: 'medium',
19
+ category: 'error',
20
+ suggestion: 'Wrap async operations in try/catch or handle at call site.'
21
+ },
22
+ // Variable shadowing and redeclaration
23
+ {
24
+ id: 'CS-ERR020',
25
+ pattern: /var\s+(\w+)[\s\S]*?var\s+\1\s*=/g,
26
+ title: 'Variable Redeclaration with var',
27
+ description: 'Same variable declared twice with var - potential bug.',
28
+ severity: 'medium',
29
+ category: 'error',
30
+ suggestion: 'Use let/const and avoid redeclaring variables.'
31
+ },
32
+ // Comparison issues
33
+ {
34
+ id: 'CS-ERR030',
35
+ pattern: /[^!=]==[^=]/g,
36
+ title: 'Loose Equality (==)',
37
+ description: 'Loose equality can cause unexpected type coercion.',
38
+ severity: 'low',
39
+ category: 'error',
40
+ suggestion: 'Use strict equality (===) instead.'
41
+ },
42
+ {
43
+ id: 'CS-ERR031',
44
+ pattern: /!=[^=]/g,
45
+ title: 'Loose Inequality (!=)',
46
+ description: 'Loose inequality can cause unexpected type coercion.',
47
+ severity: 'low',
48
+ category: 'error',
49
+ suggestion: 'Use strict inequality (!==) instead.'
50
+ },
51
+ {
52
+ id: 'CS-ERR032',
53
+ pattern: /if\s*\(\s*\w+\s*=\s*[^=]/g,
54
+ title: 'Assignment in Condition',
55
+ description: 'Assignment inside if condition - likely meant to use ==.',
56
+ severity: 'high',
57
+ category: 'error',
58
+ suggestion: 'Use === for comparison. If assignment is intentional, wrap in extra parens.'
59
+ },
60
+ // Null/undefined issues
61
+ {
62
+ id: 'CS-ERR040',
63
+ pattern: /(\w+)\.(\w+)\s*(?:\(|\.)/g,
64
+ title: 'Potential Null Reference',
65
+ description: 'Accessing property without null check - may throw.',
66
+ severity: 'low',
67
+ category: 'error',
68
+ suggestion: 'Use optional chaining (?.) or add null checks.'
69
+ },
70
+ // Loop issues
71
+ {
72
+ id: 'CS-ERR050',
73
+ pattern: /for\s*\(\s*(?:var|let)\s+\w+\s+in\s+/g,
74
+ title: 'for...in on Array',
75
+ description: 'for...in iterates over keys, not values - often wrong for arrays.',
76
+ severity: 'medium',
77
+ category: 'error',
78
+ suggestion: 'Use for...of, forEach(), or traditional for loop for arrays.'
79
+ },
80
+ {
81
+ id: 'CS-ERR051',
82
+ pattern: /while\s*\(\s*true\s*\)/g,
83
+ title: 'Infinite Loop Pattern',
84
+ description: 'while(true) requires explicit break - easy to create infinite loop.',
85
+ severity: 'medium',
86
+ category: 'error',
87
+ suggestion: 'Ensure there is always a reachable break condition.'
88
+ },
89
+ // Floating point comparison
90
+ {
91
+ id: 'CS-ERR060',
92
+ pattern: /(?:\d+\.\d+|\w+)\s*===?\s*(?:\d+\.\d+|\w+).*?(?:price|amount|total|sum|money|currency|rate)/gi,
93
+ title: 'Floating Point Comparison',
94
+ description: 'Direct comparison of floats (especially money) can fail due to precision.',
95
+ severity: 'high',
96
+ category: 'error',
97
+ suggestion: 'Use epsilon comparison or integer cents for money.'
98
+ },
99
+ // Array mutation issues
100
+ {
101
+ id: 'CS-ERR070',
102
+ pattern: /\.forEach\s*\([^)]*\)\s*\{[^}]*(?:\.push|\.pop|\.shift|\.splice)/g,
103
+ title: 'Array Mutation During Iteration',
104
+ description: 'Modifying array while iterating can cause skipped elements.',
105
+ severity: 'high',
106
+ category: 'error',
107
+ suggestion: 'Create a new array or iterate backwards when mutating.'
108
+ },
109
+ // parseInt without radix
110
+ {
111
+ id: 'CS-ERR080',
112
+ pattern: /parseInt\s*\(\s*[^,)]+\s*\)(?!\s*,)/g,
113
+ title: 'parseInt Without Radix',
114
+ description: 'parseInt without radix can give unexpected results.',
115
+ severity: 'low',
116
+ category: 'error',
117
+ suggestion: 'Always specify radix: parseInt(x, 10).'
118
+ },
119
+ // Unreachable code
120
+ {
121
+ id: 'CS-ERR090',
122
+ pattern: /return\s+[^;]+;\s*\n\s*(?![\s}]|case\s|default:)/g,
123
+ title: 'Potentially Unreachable Code',
124
+ description: 'Code after return statement will never execute.',
125
+ severity: 'medium',
126
+ category: 'error',
127
+ suggestion: 'Remove unreachable code or fix control flow.'
128
+ },
129
+ // Dangerous delete
130
+ {
131
+ id: 'CS-ERR100',
132
+ pattern: /delete\s+\w+\[\w+\]/g,
133
+ title: 'delete Operator on Array',
134
+ description: 'delete leaves holes in arrays - length unchanged.',
135
+ severity: 'medium',
136
+ category: 'error',
137
+ suggestion: 'Use splice() to remove array elements.'
138
+ },
139
+ // This binding issues
140
+ {
141
+ id: 'CS-ERR110',
142
+ pattern: /setTimeout\s*\(\s*(?:this\.\w+|function\s*\([^)]*\)\s*\{[^}]*this\.)/g,
143
+ title: 'Potential "this" Binding Issue',
144
+ description: 'Using "this" in setTimeout callback may not refer to expected context.',
145
+ severity: 'medium',
146
+ category: 'error',
147
+ suggestion: 'Use arrow function or .bind(this).'
148
+ },
149
+ // Constructor without new
150
+ {
151
+ id: 'CS-ERR120',
152
+ pattern: /(?:^|[^.])\b(?:Date|Array|Object|Map|Set|Promise)\s*\(\s*\)/g,
153
+ title: 'Constructor Without "new"',
154
+ description: 'Calling constructor without new may not work as expected.',
155
+ severity: 'low',
156
+ category: 'error',
157
+ suggestion: 'Use "new" keyword with constructors.'
158
+ },
159
+ // Magic numbers
160
+ {
161
+ id: 'CS-ERR130',
162
+ pattern: /(?:if|while|for|===?|!==?|[<>]=?)\s*\(?\s*(?:\d{3,}|\d+\.\d+)\s*(?!\s*(?:px|em|rem|%|vh|vw|s|ms))/g,
163
+ title: 'Magic Number',
164
+ description: 'Hardcoded number without context makes code hard to maintain.',
165
+ severity: 'low',
166
+ category: 'error',
167
+ suggestion: 'Extract magic numbers into named constants.'
168
+ },
169
+ // Async in constructor
170
+ {
171
+ id: 'CS-ERR140',
172
+ pattern: /constructor\s*\([^)]*\)\s*\{[^}]*await\s+/g,
173
+ title: 'Await in Constructor',
174
+ description: 'Constructors cannot be async - await will not work as expected.',
175
+ severity: 'high',
176
+ category: 'error',
177
+ suggestion: 'Use a static factory method for async initialization.'
178
+ }
179
+ ];
180
+ export function analyzeErrors(code, filename) {
181
+ const issues = [];
182
+ const lines = code.split('\n');
183
+ for (const patternDef of errorPatterns) {
184
+ patternDef.pattern.lastIndex = 0;
185
+ let match;
186
+ while ((match = patternDef.pattern.exec(code)) !== null) {
187
+ const beforeMatch = code.substring(0, match.index);
188
+ const lineNumber = beforeMatch.split('\n').length;
189
+ const lineContent = lines[lineNumber - 1] || '';
190
+ // Build verification with actual values substituted
191
+ let verification = patternDef.verification;
192
+ if (verification) {
193
+ verification = {
194
+ ...verification,
195
+ commands: verification.commands?.map(cmd => cmd.replace(/<filename>/g, filename))
196
+ };
197
+ }
198
+ issues.push({
199
+ id: patternDef.id,
200
+ category: patternDef.category,
201
+ severity: patternDef.severity,
202
+ title: patternDef.title,
203
+ description: patternDef.description,
204
+ line: lineNumber,
205
+ code: lineContent.trim(),
206
+ suggestion: patternDef.suggestion,
207
+ verification
208
+ });
209
+ if (!patternDef.pattern.global)
210
+ break;
211
+ }
212
+ }
213
+ return issues;
214
+ }