archrisk-engine 1.0.1 → 1.0.2
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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/jsAnalyzer.d.ts +8 -0
- package/dist/jsAnalyzer.js +129 -0
- package/dist/repoAnalyzer.js +25 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/jsAnalyzer.ts +143 -0
- package/src/repoAnalyzer.ts +28 -0
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -19,3 +19,4 @@ __exportStar(require("./aiDiagnosis.js"), exports);
|
|
|
19
19
|
__exportStar(require("./archScanner.js"), exports);
|
|
20
20
|
__exportStar(require("./repoAnalyzer.js"), exports);
|
|
21
21
|
__exportStar(require("./deepAnalysis.js"), exports);
|
|
22
|
+
__exportStar(require("./jsAnalyzer.js"), exports);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AnalysisResult } from './analyzer.js';
|
|
2
|
+
/**
|
|
3
|
+
* [The Eye] JS/TS Analysis Engine
|
|
4
|
+
*
|
|
5
|
+
* RegEx-based static analysis for JavaScript/TypeScript files.
|
|
6
|
+
* MVP: Focused on 8 specific credibility rules.
|
|
7
|
+
*/
|
|
8
|
+
export declare function analyzeJsTsCode(code: string, fileName: string): Promise<AnalysisResult>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyzeJsTsCode = analyzeJsTsCode;
|
|
4
|
+
/**
|
|
5
|
+
* [The Eye] JS/TS Analysis Engine
|
|
6
|
+
*
|
|
7
|
+
* RegEx-based static analysis for JavaScript/TypeScript files.
|
|
8
|
+
* MVP: Focused on 8 specific credibility rules.
|
|
9
|
+
*/
|
|
10
|
+
async function analyzeJsTsCode(code, fileName) {
|
|
11
|
+
// 1. Architecture Health Scan (God Module / Large File)
|
|
12
|
+
const lines = code.split('\n');
|
|
13
|
+
if (lines.length > 800) {
|
|
14
|
+
return {
|
|
15
|
+
hasError: true,
|
|
16
|
+
error: `Large File Risk: ${lines.length} lines. Maintanability risk.`,
|
|
17
|
+
line: 1,
|
|
18
|
+
type: 'ProductionRisk',
|
|
19
|
+
file: fileName
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// 2. Risk Scan (Security & Production Readiness)
|
|
23
|
+
const riskResult = scanForJsRisks(code, fileName, lines);
|
|
24
|
+
if (riskResult.hasError) {
|
|
25
|
+
return riskResult;
|
|
26
|
+
}
|
|
27
|
+
return { hasError: false };
|
|
28
|
+
}
|
|
29
|
+
function scanForJsRisks(code, fileName, lines) {
|
|
30
|
+
const risks = [
|
|
31
|
+
// Critical (🔴) - Security
|
|
32
|
+
{
|
|
33
|
+
pattern: /eval\s*\(|new\s+Function\s*\(/,
|
|
34
|
+
type: 'SecurityRisk',
|
|
35
|
+
message: '[Security] Dynamic code execution detected (eval/new Function). This is a severe security risk.'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
pattern: /child_process\.(exec|execSync)\s*\(/,
|
|
39
|
+
type: 'SecurityRisk',
|
|
40
|
+
message: '[Security] Shell command execution detected. Ensure inputs are sanitized or use spawn without shell.'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
pattern: /spawn\s*\(.*,\s*\{.*shell:\s*true/s, // Multi-line match attempt with DOTALL flag simulated or just simple check
|
|
44
|
+
type: 'SecurityRisk',
|
|
45
|
+
message: '[Security] spawn with { shell: true } detected. This enables shell command injection.'
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
pattern: /(?:api[._-]?key|password|secret|token)\s*[:=]\s*['"][a-zA-Z0-9_-]{10,}['"]/i,
|
|
49
|
+
type: 'SecurityRisk',
|
|
50
|
+
message: '[Security] Hardcoded secret detected. Use environment variables.'
|
|
51
|
+
},
|
|
52
|
+
// Warning (🟡) - Production Readiness
|
|
53
|
+
{
|
|
54
|
+
condition: (l) => /(axios(\.[a-z]+)?|fetch|http\.(get|request))\s*\(/.test(l) && !/timeout/.test(l),
|
|
55
|
+
type: 'ProductionRisk',
|
|
56
|
+
message: '[Reliability] HTTP call missing explicit timeout. This can cause cascading failures.'
|
|
57
|
+
},
|
|
58
|
+
// Heuristic: App listen but no global error handler (simplified: check for generic app.use((err... pattern)
|
|
59
|
+
{
|
|
60
|
+
condition: (c) => c.includes('app.listen') && !/app\.use\s*\(\s*\(\s*err/.test(c),
|
|
61
|
+
type: 'ProductionRisk',
|
|
62
|
+
message: '[Reliability] Express app detected but Global Error Handler missing. Unhandled errors may crash the server.'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
pattern: /console\.log\s*\(/,
|
|
66
|
+
type: 'ProductionRisk',
|
|
67
|
+
message: '[Observability] console.log used in production code. Use a structured logger (winston/pino).'
|
|
68
|
+
}
|
|
69
|
+
];
|
|
70
|
+
// Line-based checks
|
|
71
|
+
for (let i = 0; i < lines.length; i++) {
|
|
72
|
+
const line = lines[i];
|
|
73
|
+
for (const risk of risks) {
|
|
74
|
+
// Pattern check
|
|
75
|
+
if (risk.pattern && risk.pattern.test(line)) {
|
|
76
|
+
// Skip comments for simple cases
|
|
77
|
+
if (line.trim().startsWith('//') || line.trim().startsWith('*'))
|
|
78
|
+
continue;
|
|
79
|
+
return {
|
|
80
|
+
hasError: true,
|
|
81
|
+
error: risk.message,
|
|
82
|
+
line: i + 1,
|
|
83
|
+
type: risk.type,
|
|
84
|
+
file: fileName
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Line-based Condition check (special case for timeout)
|
|
88
|
+
// We distinguish global vs line condition by context?
|
|
89
|
+
// The 'app.listen' check is clearly global (checks whole code).
|
|
90
|
+
// The 'timeout' check is clearly line based (checks 'l').
|
|
91
|
+
// Let's rely on the message content to know if it is line-based for MVP simplicity
|
|
92
|
+
if (risk.condition && risk.message.includes('HTTP missing timeout') && risk.condition(line)) {
|
|
93
|
+
if (line.trim().startsWith('//') || line.trim().startsWith('*'))
|
|
94
|
+
continue;
|
|
95
|
+
return {
|
|
96
|
+
hasError: true,
|
|
97
|
+
error: risk.message,
|
|
98
|
+
line: i + 1,
|
|
99
|
+
type: risk.type,
|
|
100
|
+
file: fileName
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// File-based checks (Global patterns)
|
|
106
|
+
for (const risk of risks) {
|
|
107
|
+
// Global Condition check
|
|
108
|
+
if (risk.condition && !risk.message.includes('HTTP missing timeout') && risk.condition(code)) {
|
|
109
|
+
return {
|
|
110
|
+
hasError: true,
|
|
111
|
+
error: risk.message,
|
|
112
|
+
line: 0,
|
|
113
|
+
type: risk.type,
|
|
114
|
+
file: fileName
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// Multi-line regex checks
|
|
118
|
+
if (risk.pattern && risk.pattern.flags.includes('s') && risk.pattern.test(code)) {
|
|
119
|
+
return {
|
|
120
|
+
hasError: true,
|
|
121
|
+
error: risk.message,
|
|
122
|
+
line: 0,
|
|
123
|
+
type: risk.type,
|
|
124
|
+
file: fileName
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { hasError: false };
|
|
129
|
+
}
|
package/dist/repoAnalyzer.js
CHANGED
|
@@ -39,6 +39,7 @@ const fs = __importStar(require("fs"));
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const child_process_1 = require("child_process");
|
|
41
41
|
const analyzer_js_1 = require("./analyzer.js");
|
|
42
|
+
const jsAnalyzer_js_1 = require("./jsAnalyzer.js");
|
|
42
43
|
const archScanner_js_1 = require("./archScanner.js");
|
|
43
44
|
/**
|
|
44
45
|
* CEO-ready "Business Translation" for technical risks
|
|
@@ -305,6 +306,30 @@ async function analyzeRepository(repoPath, resultsDir) {
|
|
|
305
306
|
console.error(`Error analyzing ${f}:`, e);
|
|
306
307
|
}
|
|
307
308
|
}
|
|
309
|
+
// 2.1 Code Level Analysis (JS/TS logic)
|
|
310
|
+
for (const f of files.filter(f => /\.(js|ts|jsx|tsx)$/.test(f))) {
|
|
311
|
+
try {
|
|
312
|
+
const code = fs.readFileSync(f, 'utf8');
|
|
313
|
+
const relativePath = path.relative(repoPath, f);
|
|
314
|
+
// Logging Check (Simple console.log check is in rules, but here specific frameworks?)
|
|
315
|
+
// We rely on rules for now.
|
|
316
|
+
const result = await (0, jsAnalyzer_js_1.analyzeJsTsCode)(code, relativePath);
|
|
317
|
+
if (result.hasError) {
|
|
318
|
+
const details = getAuditDetails('RR-SEC-002', result.type || 'Error', result.error || 'Unknown Issue'); // Use SEC-002 for JS? Or reuse.
|
|
319
|
+
findings.push({
|
|
320
|
+
file: relativePath,
|
|
321
|
+
line: result.line || 0,
|
|
322
|
+
type: result.type || 'Error',
|
|
323
|
+
...details
|
|
324
|
+
});
|
|
325
|
+
if (result.type === 'SecurityRisk')
|
|
326
|
+
criticalCount++;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch (e) {
|
|
330
|
+
console.error(`Error analyzing ${f}:`, e);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
308
333
|
if (!hasLogging && files.filter(f => f.endsWith('.py')).length > 0) {
|
|
309
334
|
const details = getAuditDetails('RR-LOG-001', 'ProductionRisk', '');
|
|
310
335
|
findings.push({ file: 'Repository Root', line: 0, type: 'ProductionRisk', ...details });
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { AnalysisResult } from './analyzer.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* [The Eye] JS/TS Analysis Engine
|
|
5
|
+
*
|
|
6
|
+
* RegEx-based static analysis for JavaScript/TypeScript files.
|
|
7
|
+
* MVP: Focused on 8 specific credibility rules.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export async function analyzeJsTsCode(code: string, fileName: string): Promise<AnalysisResult> {
|
|
11
|
+
// 1. Architecture Health Scan (God Module / Large File)
|
|
12
|
+
const lines = code.split('\n');
|
|
13
|
+
if (lines.length > 800) {
|
|
14
|
+
return {
|
|
15
|
+
hasError: true,
|
|
16
|
+
error: `Large File Risk: ${lines.length} lines. Maintanability risk.`,
|
|
17
|
+
line: 1,
|
|
18
|
+
type: 'ProductionRisk',
|
|
19
|
+
file: fileName
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 2. Risk Scan (Security & Production Readiness)
|
|
24
|
+
const riskResult = scanForJsRisks(code, fileName, lines);
|
|
25
|
+
if (riskResult.hasError) {
|
|
26
|
+
return riskResult;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { hasError: false };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface RiskRule {
|
|
33
|
+
pattern?: RegExp;
|
|
34
|
+
condition?: (codeOrLine: string) => boolean;
|
|
35
|
+
type: string;
|
|
36
|
+
message: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function scanForJsRisks(code: string, fileName: string, lines: string[]): AnalysisResult {
|
|
40
|
+
const risks: RiskRule[] = [
|
|
41
|
+
// Critical (🔴) - Security
|
|
42
|
+
{
|
|
43
|
+
pattern: /eval\s*\(|new\s+Function\s*\(/,
|
|
44
|
+
type: 'SecurityRisk',
|
|
45
|
+
message: '[Security] Dynamic code execution detected (eval/new Function). This is a severe security risk.'
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
pattern: /child_process\.(exec|execSync)\s*\(/,
|
|
49
|
+
type: 'SecurityRisk',
|
|
50
|
+
message: '[Security] Shell command execution detected. Ensure inputs are sanitized or use spawn without shell.'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
pattern: /spawn\s*\(.*,\s*\{.*shell:\s*true/s, // Multi-line match attempt with DOTALL flag simulated or just simple check
|
|
54
|
+
type: 'SecurityRisk',
|
|
55
|
+
message: '[Security] spawn with { shell: true } detected. This enables shell command injection.'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
pattern: /(?:api[._-]?key|password|secret|token)\s*[:=]\s*['"][a-zA-Z0-9_-]{10,}['"]/i,
|
|
59
|
+
type: 'SecurityRisk',
|
|
60
|
+
message: '[Security] Hardcoded secret detected. Use environment variables.'
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Warning (🟡) - Production Readiness
|
|
64
|
+
{
|
|
65
|
+
condition: (l) => /(axios(\.[a-z]+)?|fetch|http\.(get|request))\s*\(/.test(l) && !/timeout/.test(l),
|
|
66
|
+
type: 'ProductionRisk',
|
|
67
|
+
message: '[Reliability] HTTP call missing explicit timeout. This can cause cascading failures.'
|
|
68
|
+
},
|
|
69
|
+
// Heuristic: App listen but no global error handler (simplified: check for generic app.use((err... pattern)
|
|
70
|
+
{
|
|
71
|
+
condition: (c) => c.includes('app.listen') && !/app\.use\s*\(\s*\(\s*err/.test(c),
|
|
72
|
+
type: 'ProductionRisk',
|
|
73
|
+
message: '[Reliability] Express app detected but Global Error Handler missing. Unhandled errors may crash the server.'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
pattern: /console\.log\s*\(/,
|
|
77
|
+
type: 'ProductionRisk',
|
|
78
|
+
message: '[Observability] console.log used in production code. Use a structured logger (winston/pino).'
|
|
79
|
+
}
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
// Line-based checks
|
|
83
|
+
for (let i = 0; i < lines.length; i++) {
|
|
84
|
+
const line = lines[i];
|
|
85
|
+
for (const risk of risks) {
|
|
86
|
+
// Pattern check
|
|
87
|
+
if (risk.pattern && risk.pattern.test(line)) {
|
|
88
|
+
// Skip comments for simple cases
|
|
89
|
+
if (line.trim().startsWith('//') || line.trim().startsWith('*')) continue;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
hasError: true,
|
|
93
|
+
error: risk.message,
|
|
94
|
+
line: i + 1,
|
|
95
|
+
type: risk.type,
|
|
96
|
+
file: fileName
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Line-based Condition check (special case for timeout)
|
|
101
|
+
// We distinguish global vs line condition by context?
|
|
102
|
+
// The 'app.listen' check is clearly global (checks whole code).
|
|
103
|
+
// The 'timeout' check is clearly line based (checks 'l').
|
|
104
|
+
// Let's rely on the message content to know if it is line-based for MVP simplicity
|
|
105
|
+
if (risk.condition && risk.message.includes('HTTP missing timeout') && risk.condition(line)) {
|
|
106
|
+
if (line.trim().startsWith('//') || line.trim().startsWith('*')) continue;
|
|
107
|
+
return {
|
|
108
|
+
hasError: true,
|
|
109
|
+
error: risk.message,
|
|
110
|
+
line: i + 1,
|
|
111
|
+
type: risk.type,
|
|
112
|
+
file: fileName
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// File-based checks (Global patterns)
|
|
119
|
+
for (const risk of risks) {
|
|
120
|
+
// Global Condition check
|
|
121
|
+
if (risk.condition && !risk.message.includes('HTTP missing timeout') && risk.condition(code)) {
|
|
122
|
+
return {
|
|
123
|
+
hasError: true,
|
|
124
|
+
error: risk.message,
|
|
125
|
+
line: 0,
|
|
126
|
+
type: risk.type,
|
|
127
|
+
file: fileName
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
// Multi-line regex checks
|
|
131
|
+
if (risk.pattern && risk.pattern.flags.includes('s') && risk.pattern.test(code)) {
|
|
132
|
+
return {
|
|
133
|
+
hasError: true,
|
|
134
|
+
error: risk.message,
|
|
135
|
+
line: 0,
|
|
136
|
+
type: risk.type,
|
|
137
|
+
file: fileName
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { hasError: false };
|
|
143
|
+
}
|
package/src/repoAnalyzer.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { execSync } from 'child_process';
|
|
4
4
|
import { analyzePythonCode, AnalysisResult } from './analyzer.js';
|
|
5
|
+
import { analyzeJsTsCode } from './jsAnalyzer.js';
|
|
5
6
|
import { depsScan } from './archScanner.js';
|
|
6
7
|
|
|
7
8
|
export interface RepoAnalysisResult {
|
|
@@ -314,6 +315,33 @@ export async function analyzeRepository(repoPath: string, resultsDir?: string):
|
|
|
314
315
|
}
|
|
315
316
|
}
|
|
316
317
|
|
|
318
|
+
// 2.1 Code Level Analysis (JS/TS logic)
|
|
319
|
+
for (const f of files.filter(f => /\.(js|ts|jsx|tsx)$/.test(f))) {
|
|
320
|
+
try {
|
|
321
|
+
const code = fs.readFileSync(f, 'utf8');
|
|
322
|
+
const relativePath = path.relative(repoPath, f);
|
|
323
|
+
|
|
324
|
+
// Logging Check (Simple console.log check is in rules, but here specific frameworks?)
|
|
325
|
+
// We rely on rules for now.
|
|
326
|
+
|
|
327
|
+
const result = await analyzeJsTsCode(code, relativePath);
|
|
328
|
+
|
|
329
|
+
if (result.hasError) {
|
|
330
|
+
const details = getAuditDetails('RR-SEC-002', result.type || 'Error', result.error || 'Unknown Issue'); // Use SEC-002 for JS? Or reuse.
|
|
331
|
+
findings.push({
|
|
332
|
+
file: relativePath,
|
|
333
|
+
line: result.line || 0,
|
|
334
|
+
type: result.type || 'Error',
|
|
335
|
+
...details
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
if (result.type === 'SecurityRisk') criticalCount++;
|
|
339
|
+
}
|
|
340
|
+
} catch (e) {
|
|
341
|
+
console.error(`Error analyzing ${f}:`, e);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
317
345
|
if (!hasLogging && files.filter(f => f.endsWith('.py')).length > 0) {
|
|
318
346
|
const details = getAuditDetails('RR-LOG-001', 'ProductionRisk', '');
|
|
319
347
|
findings.push({ file: 'Repository Root', line: 0, type: 'ProductionRisk', ...details });
|