@avinashchby/aireview 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/LICENSE +21 -0
- package/README.md +157 -0
- package/bin/aireview.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +79 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +78 -0
- package/dist/config.js.map +1 -0
- package/dist/fixer/index.d.ts +4 -0
- package/dist/fixer/index.d.ts.map +1 -0
- package/dist/fixer/index.js +54 -0
- package/dist/fixer/index.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/index.d.ts +8 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +47 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/python-parser.d.ts +4 -0
- package/dist/parsers/python-parser.d.ts.map +1 -0
- package/dist/parsers/python-parser.js +235 -0
- package/dist/parsers/python-parser.js.map +1 -0
- package/dist/parsers/typescript-parser.d.ts +4 -0
- package/dist/parsers/typescript-parser.d.ts.map +1 -0
- package/dist/parsers/typescript-parser.js +300 -0
- package/dist/parsers/typescript-parser.js.map +1 -0
- package/dist/reporters/ci-reporter.d.ts +4 -0
- package/dist/reporters/ci-reporter.d.ts.map +1 -0
- package/dist/reporters/ci-reporter.js +49 -0
- package/dist/reporters/ci-reporter.js.map +1 -0
- package/dist/reporters/index.d.ts +6 -0
- package/dist/reporters/index.d.ts.map +1 -0
- package/dist/reporters/index.js +15 -0
- package/dist/reporters/index.js.map +1 -0
- package/dist/reporters/json-reporter.d.ts +4 -0
- package/dist/reporters/json-reporter.d.ts.map +1 -0
- package/dist/reporters/json-reporter.js +8 -0
- package/dist/reporters/json-reporter.js.map +1 -0
- package/dist/reporters/text-reporter.d.ts +4 -0
- package/dist/reporters/text-reporter.d.ts.map +1 -0
- package/dist/reporters/text-reporter.js +110 -0
- package/dist/reporters/text-reporter.js.map +1 -0
- package/dist/rules/confidence-patterns.d.ts +4 -0
- package/dist/rules/confidence-patterns.d.ts.map +1 -0
- package/dist/rules/confidence-patterns.js +186 -0
- package/dist/rules/confidence-patterns.js.map +1 -0
- package/dist/rules/engine.d.ts +19 -0
- package/dist/rules/engine.d.ts.map +1 -0
- package/dist/rules/engine.js +58 -0
- package/dist/rules/engine.js.map +1 -0
- package/dist/rules/error-handling.d.ts +4 -0
- package/dist/rules/error-handling.d.ts.map +1 -0
- package/dist/rules/error-handling.js +162 -0
- package/dist/rules/error-handling.js.map +1 -0
- package/dist/rules/hallucinated-apis.d.ts +4 -0
- package/dist/rules/hallucinated-apis.d.ts.map +1 -0
- package/dist/rules/hallucinated-apis.js +196 -0
- package/dist/rules/hallucinated-apis.js.map +1 -0
- package/dist/rules/index.d.ts +4 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +27 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/phantom-imports.d.ts +4 -0
- package/dist/rules/phantom-imports.d.ts.map +1 -0
- package/dist/rules/phantom-imports.js +300 -0
- package/dist/rules/phantom-imports.js.map +1 -0
- package/dist/rules/placeholder-code.d.ts +4 -0
- package/dist/rules/placeholder-code.d.ts.map +1 -0
- package/dist/rules/placeholder-code.js +113 -0
- package/dist/rules/placeholder-code.js.map +1 -0
- package/dist/rules/security-antipatterns.d.ts +4 -0
- package/dist/rules/security-antipatterns.d.ts.map +1 -0
- package/dist/rules/security-antipatterns.js +174 -0
- package/dist/rules/security-antipatterns.js.map +1 -0
- package/dist/rules/type-safety.d.ts +4 -0
- package/dist/rules/type-safety.d.ts.map +1 -0
- package/dist/rules/type-safety.js +148 -0
- package/dist/rules/type-safety.js.map +1 -0
- package/dist/scanner/discovery.d.ts +3 -0
- package/dist/scanner/discovery.d.ts.map +1 -0
- package/dist/scanner/discovery.js +76 -0
- package/dist/scanner/discovery.js.map +1 -0
- package/dist/scanner/git-diff.d.ts +8 -0
- package/dist/scanner/git-diff.d.ts.map +1 -0
- package/dist/scanner/git-diff.js +58 -0
- package/dist/scanner/git-diff.js.map +1 -0
- package/dist/scanner/index.d.ts +4 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +110 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/types.d.ts +150 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.placeholderCodeRule = void 0;
|
|
4
|
+
/** Comment patterns indicating placeholder/stub code. */
|
|
5
|
+
const PLACEHOLDER_COMMENT_PATTERNS = [
|
|
6
|
+
{ pattern: /\bTODO\b/i, severity: 'warning', label: 'TODO comment' },
|
|
7
|
+
{ pattern: /\bFIXME\b/i, severity: 'warning', label: 'FIXME comment' },
|
|
8
|
+
{ pattern: /\bHACK\b/i, severity: 'warning', label: 'HACK comment' },
|
|
9
|
+
{ pattern: /\bXXX\b/i, severity: 'warning', label: 'XXX comment' },
|
|
10
|
+
{ pattern: /implement\s+this/i, severity: 'info', label: 'Placeholder: "implement this"' },
|
|
11
|
+
{ pattern: /add\s+your\s+code\s+here/i, severity: 'info', label: 'Placeholder: "add your code here"' },
|
|
12
|
+
{ pattern: /your\s+code\s+here/i, severity: 'info', label: 'Placeholder: "your code here"' },
|
|
13
|
+
{ pattern: /implement\s+me/i, severity: 'info', label: 'Placeholder: "implement me"' },
|
|
14
|
+
{ pattern: /add\s+implementation/i, severity: 'info', label: 'Placeholder: "add implementation"' },
|
|
15
|
+
{ pattern: /\bplaceholder\b/i, severity: 'info', label: 'Placeholder comment' },
|
|
16
|
+
{ pattern: /not\s+yet\s+implemented/i, severity: 'info', label: 'Placeholder: "not yet implemented"' },
|
|
17
|
+
];
|
|
18
|
+
/** Patterns for elided code comments. */
|
|
19
|
+
const ELIDED_COMMENT = /^(\/\/|#)\s*\.\.\.$/;
|
|
20
|
+
/** Create a finding for the placeholder-code rule. */
|
|
21
|
+
function makeFinding(line, column, snippet, message, explanation, severity, filePath) {
|
|
22
|
+
return {
|
|
23
|
+
ruleId: 'placeholder-code',
|
|
24
|
+
category: 'placeholder-code',
|
|
25
|
+
severity,
|
|
26
|
+
message,
|
|
27
|
+
filePath,
|
|
28
|
+
line,
|
|
29
|
+
column,
|
|
30
|
+
snippet,
|
|
31
|
+
explanation,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Check comments for placeholder patterns. */
|
|
35
|
+
function checkComments(parserResult, filePath) {
|
|
36
|
+
const findings = [];
|
|
37
|
+
for (const comment of parserResult.comments) {
|
|
38
|
+
for (const { pattern, severity, label } of PLACEHOLDER_COMMENT_PATTERNS) {
|
|
39
|
+
if (pattern.test(comment.text)) {
|
|
40
|
+
findings.push(makeFinding(comment.line, comment.column, comment.text, `${label} found`, 'Replace placeholder code with actual implementation.', severity, filePath));
|
|
41
|
+
break; // Only flag once per comment
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return findings;
|
|
46
|
+
}
|
|
47
|
+
/** Check function declarations for empty/stub bodies. */
|
|
48
|
+
function checkFunctionDecls(parserResult, filePath) {
|
|
49
|
+
const findings = [];
|
|
50
|
+
for (const fn of parserResult.functionDecls) {
|
|
51
|
+
if (fn.isEmpty || fn.bodyLineCount === 0) {
|
|
52
|
+
findings.push(makeFinding(fn.line, 1, `function ${fn.name}`, `Empty function body: '${fn.name}'`, 'Empty functions suggest incomplete implementation. Add the function logic.', 'error', filePath));
|
|
53
|
+
}
|
|
54
|
+
else if (fn.returnsNullish) {
|
|
55
|
+
findings.push(makeFinding(fn.line, 1, `function ${fn.name}`, `Function '${fn.name}' only returns a nullish/empty value`, 'Functions returning only null/undefined/empty are likely stubs.', 'error', filePath));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return findings;
|
|
59
|
+
}
|
|
60
|
+
/** Check source lines for placeholder patterns not captured by parser. */
|
|
61
|
+
function checkLines(parserResult, filePath) {
|
|
62
|
+
const findings = [];
|
|
63
|
+
const lines = parserResult.lines;
|
|
64
|
+
for (let i = 0; i < lines.length; i++) {
|
|
65
|
+
const line = lines[i];
|
|
66
|
+
const trimmed = line.trim();
|
|
67
|
+
const lineNum = i + 1;
|
|
68
|
+
checkThrowPatterns(trimmed, lineNum, filePath, findings);
|
|
69
|
+
checkElided(trimmed, lineNum, filePath, findings);
|
|
70
|
+
checkTestOnlyStatements(trimmed, lineNum, filePath, findings);
|
|
71
|
+
}
|
|
72
|
+
return findings;
|
|
73
|
+
}
|
|
74
|
+
/** Check for NotImplementedError / "Not implemented" throws. */
|
|
75
|
+
function checkThrowPatterns(trimmed, lineNum, filePath, findings) {
|
|
76
|
+
if (/throw\s+new\s+Error\s*\(\s*['"]Not implemented/.test(trimmed)) {
|
|
77
|
+
findings.push(makeFinding(lineNum, 1, trimmed, '"Not implemented" error thrown', 'Replace the thrown error with actual implementation.', 'error', filePath));
|
|
78
|
+
}
|
|
79
|
+
if (/raise\s+NotImplementedError/.test(trimmed)) {
|
|
80
|
+
findings.push(makeFinding(lineNum, 1, trimmed, 'NotImplementedError raised', 'Replace NotImplementedError with actual implementation.', 'error', filePath));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Check for elided code comments like "// ..." or "# ...". */
|
|
84
|
+
function checkElided(trimmed, lineNum, filePath, findings) {
|
|
85
|
+
if (ELIDED_COMMENT.test(trimmed)) {
|
|
86
|
+
findings.push(makeFinding(lineNum, 1, trimmed, 'Elided code comment suggests missing implementation', 'The "..." comment indicates code was omitted. Add the missing logic.', 'info', filePath));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Check for test-only statements (console.log/print as only line). */
|
|
90
|
+
function checkTestOnlyStatements(trimmed, lineNum, filePath, findings) {
|
|
91
|
+
if (/^console\.log\s*\(\s*['"]test['"]\s*\)\s*;?\s*$/.test(trimmed)) {
|
|
92
|
+
findings.push(makeFinding(lineNum, 1, trimmed, 'console.log("test") appears to be placeholder code', 'Replace test logging with actual implementation.', 'warning', filePath));
|
|
93
|
+
}
|
|
94
|
+
if (/^print\s*\(\s*['"]test['"]\s*\)\s*$/.test(trimmed)) {
|
|
95
|
+
findings.push(makeFinding(lineNum, 1, trimmed, 'print("test") appears to be placeholder code', 'Replace test printing with actual implementation.', 'warning', filePath));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** The placeholder-code rule. */
|
|
99
|
+
exports.placeholderCodeRule = {
|
|
100
|
+
id: 'placeholder-code',
|
|
101
|
+
category: 'placeholder-code',
|
|
102
|
+
severity: 'warning',
|
|
103
|
+
languages: ['javascript', 'typescript', 'python'],
|
|
104
|
+
description: 'Detect placeholder/stub code commonly left by LLMs.',
|
|
105
|
+
check(parserResult, filePath) {
|
|
106
|
+
const findings = [];
|
|
107
|
+
findings.push(...checkComments(parserResult, filePath));
|
|
108
|
+
findings.push(...checkFunctionDecls(parserResult, filePath));
|
|
109
|
+
findings.push(...checkLines(parserResult, filePath));
|
|
110
|
+
return findings;
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=placeholder-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placeholder-code.js","sourceRoot":"","sources":["../../src/rules/placeholder-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-antipatterns.d.ts","sourceRoot":"","sources":["../../src/rules/security-antipatterns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAyB,MAAM,aAAa,CAAC;AAgQ1D,mEAAmE;AACnE,eAAO,MAAM,wBAAwB,EAAE,IAOtC,CAAC"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.securityAntipatternsRule = void 0;
|
|
4
|
+
const RULE_ID = 'security-antipatterns';
|
|
5
|
+
const CATEGORY = 'security-antipatterns';
|
|
6
|
+
/** Keywords that precede hardcoded secret values. */
|
|
7
|
+
const SECRET_KEYWORDS = [
|
|
8
|
+
'key', 'secret', 'token', 'password', 'api_key', 'apiKey',
|
|
9
|
+
'API_KEY', 'SECRET', 'TOKEN', 'PASSWORD', 'aws_access_key',
|
|
10
|
+
'PRIVATE_KEY',
|
|
11
|
+
];
|
|
12
|
+
/** Build a finding with standard fields pre-filled. */
|
|
13
|
+
function makeFinding(filePath, line, snippet, severity, message, explanation) {
|
|
14
|
+
return {
|
|
15
|
+
ruleId: RULE_ID,
|
|
16
|
+
category: CATEGORY,
|
|
17
|
+
severity,
|
|
18
|
+
message,
|
|
19
|
+
filePath,
|
|
20
|
+
line,
|
|
21
|
+
column: 0,
|
|
22
|
+
snippet,
|
|
23
|
+
explanation,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Detect eval/exec/Function constructor calls from AST data. */
|
|
27
|
+
function detectDangerousCalls(pr, filePath) {
|
|
28
|
+
const findings = [];
|
|
29
|
+
const lang = pr.language;
|
|
30
|
+
for (const call of pr.functionCalls) {
|
|
31
|
+
const snippet = pr.lines[call.line - 1] ?? '';
|
|
32
|
+
if (call.method === 'eval' && !call.object) {
|
|
33
|
+
findings.push(makeFinding(filePath, call.line, snippet, 'error', 'eval() is a code injection vector', 'eval() executes arbitrary strings as code, enabling injection attacks. ' +
|
|
34
|
+
'Use JSON.parse() for data, or a sandboxed interpreter if dynamic execution is required.'));
|
|
35
|
+
}
|
|
36
|
+
if (call.method === 'exec' && !call.object && lang === 'python') {
|
|
37
|
+
findings.push(makeFinding(filePath, call.line, snippet, 'error', 'exec() executes arbitrary code at runtime', 'exec() can run any Python code from a string, enabling injection. ' +
|
|
38
|
+
'Use ast.literal_eval() for safe data parsing or refactor to avoid dynamic execution.'));
|
|
39
|
+
}
|
|
40
|
+
if (call.method === 'Function' && !call.object) {
|
|
41
|
+
findings.push(makeFinding(filePath, call.line, snippet, 'error', 'Function() constructor is equivalent to eval()', 'new Function() compiles strings into executable code, same risk as eval(). ' +
|
|
42
|
+
'Refactor to use static functions or a safe template engine.'));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return findings;
|
|
46
|
+
}
|
|
47
|
+
/** Detect innerHTML assignments in source lines. */
|
|
48
|
+
function detectInnerHtml(pr, filePath) {
|
|
49
|
+
const findings = [];
|
|
50
|
+
for (let i = 0; i < pr.lines.length; i++) {
|
|
51
|
+
const line = pr.lines[i];
|
|
52
|
+
if (/\.innerHTML\s*[+=]/.test(line)) {
|
|
53
|
+
findings.push(makeFinding(filePath, i + 1, line, 'warning', 'innerHTML assignment enables XSS attacks', 'Setting innerHTML with user-controlled data allows script injection. ' +
|
|
54
|
+
'Use textContent for text, or a sanitizer like DOMPurify for HTML content.'));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return findings;
|
|
58
|
+
}
|
|
59
|
+
/** Detect dangerouslySetInnerHTML in React code. */
|
|
60
|
+
function detectDangerousJsx(pr, filePath) {
|
|
61
|
+
const findings = [];
|
|
62
|
+
for (let i = 0; i < pr.lines.length; i++) {
|
|
63
|
+
if (pr.lines[i].includes('dangerouslySetInnerHTML')) {
|
|
64
|
+
findings.push(makeFinding(filePath, i + 1, pr.lines[i], 'warning', 'dangerouslySetInnerHTML bypasses React XSS protections', 'This intentionally bypasses React\'s built-in escaping. Ensure the HTML is sanitized ' +
|
|
65
|
+
'with DOMPurify or a similar library before rendering.'));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return findings;
|
|
69
|
+
}
|
|
70
|
+
/** SQL keywords used to detect injection patterns. */
|
|
71
|
+
const SQL_KW = /\b(SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|UNION)\b/i;
|
|
72
|
+
/** Detect SQL string concatenation and template injection. */
|
|
73
|
+
function detectSqlInjection(pr, filePath) {
|
|
74
|
+
const findings = [];
|
|
75
|
+
for (let i = 0; i < pr.lines.length; i++) {
|
|
76
|
+
const line = pr.lines[i];
|
|
77
|
+
if (!SQL_KW.test(line))
|
|
78
|
+
continue;
|
|
79
|
+
const hasConcatInjection = /["'`].*\b(SELECT|INSERT|UPDATE|DELETE|DROP)\b.*["'`]\s*\+/.test(line) ||
|
|
80
|
+
/\+\s*["'`].*\b(SELECT|INSERT|UPDATE|DELETE|DROP)\b/.test(line);
|
|
81
|
+
const hasFstring = /f["'].*\b(SELECT|INSERT|UPDATE|DELETE|DROP)\b.*\{/.test(line);
|
|
82
|
+
const hasTemplate = /`.*\b(SELECT|INSERT|UPDATE|DELETE|DROP)\b.*\$\{/.test(line);
|
|
83
|
+
if (hasConcatInjection || hasFstring || hasTemplate) {
|
|
84
|
+
findings.push(makeFinding(filePath, i + 1, line, 'error', 'SQL string concatenation enables SQL injection', 'Building SQL queries with string concatenation or interpolation allows injection attacks. ' +
|
|
85
|
+
'Use parameterized queries (? placeholders) or an ORM\'s query builder.'));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return findings;
|
|
89
|
+
}
|
|
90
|
+
/** Regex for hardcoded secret assignment patterns. */
|
|
91
|
+
const SECRET_ASSIGN_RE = new RegExp(`(?:${SECRET_KEYWORDS.join('|')})\\s*[:=]\\s*["'\`]([A-Za-z0-9_\\-/+]{20,})["'\`]`, 'i');
|
|
92
|
+
const BEARER_RE = /Bearer\s+[A-Za-z0-9_\-/+.]{20,}/;
|
|
93
|
+
/** Detect hardcoded secrets in source lines. */
|
|
94
|
+
function detectHardcodedSecrets(pr, filePath) {
|
|
95
|
+
const findings = [];
|
|
96
|
+
for (let i = 0; i < pr.lines.length; i++) {
|
|
97
|
+
const line = pr.lines[i];
|
|
98
|
+
if (SECRET_ASSIGN_RE.test(line)) {
|
|
99
|
+
findings.push(makeFinding(filePath, i + 1, line, 'error', 'Hardcoded secret detected', 'Secrets in source code can leak via version control. ' +
|
|
100
|
+
'Use environment variables or a secrets manager (e.g., AWS Secrets Manager, Vault).'));
|
|
101
|
+
}
|
|
102
|
+
else if (BEARER_RE.test(line)) {
|
|
103
|
+
findings.push(makeFinding(filePath, i + 1, line, 'error', 'Hardcoded Bearer token detected', 'Bearer tokens in source code are a credential leak risk. ' +
|
|
104
|
+
'Load tokens from environment variables or a secure credential store.'));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return findings;
|
|
108
|
+
}
|
|
109
|
+
/** Detect shell=True in subprocess calls. */
|
|
110
|
+
function detectShellTrue(pr, filePath) {
|
|
111
|
+
if (pr.language !== 'python')
|
|
112
|
+
return [];
|
|
113
|
+
const findings = [];
|
|
114
|
+
for (let i = 0; i < pr.lines.length; i++) {
|
|
115
|
+
if (/subprocess\.\w+\(.*shell\s*=\s*True/.test(pr.lines[i])) {
|
|
116
|
+
findings.push(makeFinding(filePath, i + 1, pr.lines[i], 'warning', 'subprocess with shell=True enables shell injection', 'shell=True passes the command through the system shell, allowing injection via ' +
|
|
117
|
+
'unsanitized input. Use subprocess.run() with a list of arguments instead.'));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return findings;
|
|
121
|
+
}
|
|
122
|
+
/** Detect child_process.exec usage in Node. */
|
|
123
|
+
function detectChildProcessExec(pr, filePath) {
|
|
124
|
+
if (pr.language === 'python')
|
|
125
|
+
return [];
|
|
126
|
+
const findings = [];
|
|
127
|
+
for (let i = 0; i < pr.lines.length; i++) {
|
|
128
|
+
if (/child_process\.exec\b/.test(pr.lines[i]) ||
|
|
129
|
+
/child_process["']]\s*\)\s*\.exec\b/.test(pr.lines[i])) {
|
|
130
|
+
findings.push(makeFinding(filePath, i + 1, pr.lines[i], 'warning', 'child_process.exec runs commands in a shell', 'exec() spawns a shell, making it vulnerable to injection. ' +
|
|
131
|
+
'Use child_process.execFile() or spawn() with an argument array instead.'));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return findings;
|
|
135
|
+
}
|
|
136
|
+
/** Detect disabled TLS verification. */
|
|
137
|
+
function detectDisabledTls(pr, filePath) {
|
|
138
|
+
const findings = [];
|
|
139
|
+
for (let i = 0; i < pr.lines.length; i++) {
|
|
140
|
+
const line = pr.lines[i];
|
|
141
|
+
if (/rejectUnauthorized\s*:\s*false/.test(line)) {
|
|
142
|
+
findings.push(makeFinding(filePath, i + 1, line, 'warning', 'TLS certificate verification is disabled', 'rejectUnauthorized: false disables certificate validation, enabling MITM attacks. ' +
|
|
143
|
+
'Remove this option or configure proper CA certificates.'));
|
|
144
|
+
}
|
|
145
|
+
if (/verify\s*=\s*False/.test(line) && pr.language === 'python') {
|
|
146
|
+
findings.push(makeFinding(filePath, i + 1, line, 'warning', 'TLS certificate verification is disabled', 'verify=False disables certificate validation in requests, enabling MITM attacks. ' +
|
|
147
|
+
'Remove this option or pass a CA bundle path.'));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return findings;
|
|
151
|
+
}
|
|
152
|
+
/** Main check: runs all sub-detectors and merges results. */
|
|
153
|
+
function check(parserResult, filePath) {
|
|
154
|
+
return [
|
|
155
|
+
...detectDangerousCalls(parserResult, filePath),
|
|
156
|
+
...detectInnerHtml(parserResult, filePath),
|
|
157
|
+
...detectDangerousJsx(parserResult, filePath),
|
|
158
|
+
...detectSqlInjection(parserResult, filePath),
|
|
159
|
+
...detectHardcodedSecrets(parserResult, filePath),
|
|
160
|
+
...detectShellTrue(parserResult, filePath),
|
|
161
|
+
...detectChildProcessExec(parserResult, filePath),
|
|
162
|
+
...detectDisabledTls(parserResult, filePath),
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
/** Rule detecting common security anti-patterns in source code. */
|
|
166
|
+
exports.securityAntipatternsRule = {
|
|
167
|
+
id: RULE_ID,
|
|
168
|
+
category: CATEGORY,
|
|
169
|
+
severity: 'error',
|
|
170
|
+
languages: ['javascript', 'typescript', 'python'],
|
|
171
|
+
description: 'Detects security anti-patterns including injection vectors, hardcoded secrets, and unsafe configurations',
|
|
172
|
+
check,
|
|
173
|
+
};
|
|
174
|
+
//# sourceMappingURL=security-antipatterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-antipatterns.js","sourceRoot":"","sources":["../../src/rules/security-antipatterns.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-safety.d.ts","sourceRoot":"","sources":["../../src/rules/type-safety.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAyB,MAAM,aAAa,CAAC;AAsM1D,+EAA+E;AAC/E,eAAO,MAAM,cAAc,EAAE,IAO5B,CAAC"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.typeSafetyRule = void 0;
|
|
4
|
+
const RULE_ID = 'type-safety';
|
|
5
|
+
const CATEGORY = 'type-safety';
|
|
6
|
+
/** Variable name segments that imply numeric type. */
|
|
7
|
+
const NUMERIC_NAMES = /\b(count|length|size|total|num|amount)\b/i;
|
|
8
|
+
/** Build a finding with standard fields pre-filled. */
|
|
9
|
+
function makeFinding(filePath, line, snippet, severity, message, explanation) {
|
|
10
|
+
return {
|
|
11
|
+
ruleId: RULE_ID,
|
|
12
|
+
category: CATEGORY,
|
|
13
|
+
severity,
|
|
14
|
+
message,
|
|
15
|
+
filePath,
|
|
16
|
+
line,
|
|
17
|
+
column: 0,
|
|
18
|
+
snippet,
|
|
19
|
+
explanation,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Check if file is TypeScript based on path. */
|
|
23
|
+
function isTsFile(filePath) {
|
|
24
|
+
return /\.tsx?$/.test(filePath);
|
|
25
|
+
}
|
|
26
|
+
/** Detect `any` type annotations from parser AST data. */
|
|
27
|
+
function detectAnyAnnotations(pr, filePath) {
|
|
28
|
+
if (!isTsFile(filePath))
|
|
29
|
+
return [];
|
|
30
|
+
const findings = [];
|
|
31
|
+
const anyAnnotations = pr.typeAnnotations.filter((ta) => ta.isAny);
|
|
32
|
+
const escalate = anyAnnotations.length > 3;
|
|
33
|
+
for (const ta of anyAnnotations) {
|
|
34
|
+
const snippet = pr.lines[ta.line - 1] ?? '';
|
|
35
|
+
const severity = escalate ? 'error' : 'warning';
|
|
36
|
+
findings.push(makeFinding(filePath, ta.line, snippet, severity, `${escalate ? 'Excessive ' : ''}\`any\` type annotation defeats TypeScript safety`, escalate
|
|
37
|
+
? `This file has ${anyAnnotations.length} \`any\` annotations (>3), indicating systematic ` +
|
|
38
|
+
'type safety bypass. Replace with specific types, generics, or `unknown` with type guards.'
|
|
39
|
+
: 'The `any` type disables all type checking. Use `unknown` with type narrowing, a ' +
|
|
40
|
+
'specific interface, or a generic type parameter instead.'));
|
|
41
|
+
}
|
|
42
|
+
return findings;
|
|
43
|
+
}
|
|
44
|
+
/** Detect `as any` casts in source lines. */
|
|
45
|
+
function detectAsAnyCasts(pr, filePath) {
|
|
46
|
+
if (!isTsFile(filePath))
|
|
47
|
+
return [];
|
|
48
|
+
const findings = [];
|
|
49
|
+
for (let i = 0; i < pr.lines.length; i++) {
|
|
50
|
+
if (/\bas\s+any\b/.test(pr.lines[i])) {
|
|
51
|
+
findings.push(makeFinding(filePath, i + 1, pr.lines[i], 'warning', '`as any` cast bypasses type checking', 'Type assertions to `any` silence the compiler but hide potential runtime errors. ' +
|
|
52
|
+
'Use a specific type assertion or add proper type narrowing logic.'));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return findings;
|
|
56
|
+
}
|
|
57
|
+
/** Detect @ts-ignore, @ts-nocheck, and bare @ts-expect-error comments. */
|
|
58
|
+
function detectTsDirectives(pr, filePath) {
|
|
59
|
+
if (!isTsFile(filePath))
|
|
60
|
+
return [];
|
|
61
|
+
const findings = [];
|
|
62
|
+
for (const comment of pr.comments) {
|
|
63
|
+
const text = comment.text;
|
|
64
|
+
const snippet = pr.lines[comment.line - 1] ?? '';
|
|
65
|
+
if (/@ts-nocheck/.test(text)) {
|
|
66
|
+
findings.push(makeFinding(filePath, comment.line, snippet, 'error', '@ts-nocheck disables type checking for the entire file', 'This directive silences all TypeScript errors in the file. ' +
|
|
67
|
+
'Remove it and fix the underlying type errors to maintain type safety.'));
|
|
68
|
+
}
|
|
69
|
+
else if (/@ts-ignore/.test(text)) {
|
|
70
|
+
findings.push(makeFinding(filePath, comment.line, snippet, 'warning', '@ts-ignore suppresses the next type error without explanation', 'Use @ts-expect-error with a description instead. It will error when the ' +
|
|
71
|
+
'suppressed issue is fixed, preventing stale suppressions.'));
|
|
72
|
+
}
|
|
73
|
+
else if (/@ts-expect-error\s*$/.test(text)) {
|
|
74
|
+
findings.push(makeFinding(filePath, comment.line, snippet, 'warning', '@ts-expect-error without explanation', 'Add a description after @ts-expect-error explaining why the suppression is ' +
|
|
75
|
+
'needed, e.g. // @ts-expect-error - legacy API returns untyped data.'));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return findings;
|
|
79
|
+
}
|
|
80
|
+
/** Detect bare `# type: ignore` in Python without specific error code. */
|
|
81
|
+
function detectPythonTypeIgnore(pr, filePath) {
|
|
82
|
+
if (pr.language !== 'python')
|
|
83
|
+
return [];
|
|
84
|
+
const findings = [];
|
|
85
|
+
for (const comment of pr.comments) {
|
|
86
|
+
if (/type:\s*ignore\s*$/.test(comment.text)) {
|
|
87
|
+
const snippet = pr.lines[comment.line - 1] ?? '';
|
|
88
|
+
findings.push(makeFinding(filePath, comment.line, snippet, 'warning', 'Bare `type: ignore` suppresses all type errors on this line', 'Specify the error code, e.g. `# type: ignore[assignment]`, so only the ' +
|
|
89
|
+
'intended error is suppressed and new issues are still caught.'));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return findings;
|
|
93
|
+
}
|
|
94
|
+
/** Detect loose equality with null/undefined. */
|
|
95
|
+
function detectLooseEquality(pr, filePath) {
|
|
96
|
+
if (pr.language === 'python')
|
|
97
|
+
return [];
|
|
98
|
+
const findings = [];
|
|
99
|
+
for (let i = 0; i < pr.lines.length; i++) {
|
|
100
|
+
const line = pr.lines[i];
|
|
101
|
+
if (/[!=]=\s*null\b/.test(line) && !/[!=]==\s*null\b/.test(line)) {
|
|
102
|
+
findings.push(makeFinding(filePath, i + 1, line, 'info', 'Loose equality with null', 'Use `=== null` or `!== null` for strict equality. Loose equality (`==`) also ' +
|
|
103
|
+
'matches undefined, which may hide bugs.'));
|
|
104
|
+
}
|
|
105
|
+
if (/[!=]=\s*undefined\b/.test(line) && !/[!=]==\s*undefined\b/.test(line)) {
|
|
106
|
+
findings.push(makeFinding(filePath, i + 1, line, 'info', 'Loose equality with undefined', 'Use `=== undefined` for strict equality. Loose equality (`==`) also matches ' +
|
|
107
|
+
'null, which may not be the intended behavior.'));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return findings;
|
|
111
|
+
}
|
|
112
|
+
/** Detect implicit boolean coercion of numeric-named variables. */
|
|
113
|
+
function detectImplicitCoercion(pr, filePath) {
|
|
114
|
+
if (pr.language === 'python')
|
|
115
|
+
return [];
|
|
116
|
+
const findings = [];
|
|
117
|
+
for (let i = 0; i < pr.lines.length; i++) {
|
|
118
|
+
const line = pr.lines[i];
|
|
119
|
+
const match = line.match(/\bif\s*\(\s*(\w+)\s*\)/);
|
|
120
|
+
if (match && NUMERIC_NAMES.test(match[1])) {
|
|
121
|
+
findings.push(makeFinding(filePath, i + 1, line, 'info', `Implicit boolean coercion of numeric variable \`${match[1]}\``, `\`${match[1]}\` looks numeric and will be falsy when 0. ` +
|
|
122
|
+
`Use an explicit check like \`${match[1]} !== 0\` or \`${match[1]} != null\` ` +
|
|
123
|
+
'to make the intent clear.'));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return findings;
|
|
127
|
+
}
|
|
128
|
+
/** Main check: runs all sub-detectors and merges results. */
|
|
129
|
+
function check(parserResult, filePath) {
|
|
130
|
+
return [
|
|
131
|
+
...detectAnyAnnotations(parserResult, filePath),
|
|
132
|
+
...detectAsAnyCasts(parserResult, filePath),
|
|
133
|
+
...detectTsDirectives(parserResult, filePath),
|
|
134
|
+
...detectPythonTypeIgnore(parserResult, filePath),
|
|
135
|
+
...detectLooseEquality(parserResult, filePath),
|
|
136
|
+
...detectImplicitCoercion(parserResult, filePath),
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
/** Rule detecting type safety issues in TypeScript, JavaScript, and Python. */
|
|
140
|
+
exports.typeSafetyRule = {
|
|
141
|
+
id: RULE_ID,
|
|
142
|
+
category: CATEGORY,
|
|
143
|
+
severity: 'warning',
|
|
144
|
+
languages: ['javascript', 'typescript', 'python'],
|
|
145
|
+
description: 'Detects type safety issues including any types, suppression directives, and loose equality',
|
|
146
|
+
check,
|
|
147
|
+
};
|
|
148
|
+
//# sourceMappingURL=type-safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-safety.js","sourceRoot":"","sources":["../../src/rules/type-safety.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/scanner/discovery.ts"],"names":[],"mappings":"AAOA,uDAAuD;AACvD,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EAAE,EACf,cAAc,GAAE,MAAM,EAAO,GAC5B,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBnB"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.discoverFiles = discoverFiles;
|
|
7
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
8
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
9
|
+
const promises_1 = require("node:fs/promises");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const node_fs_1 = require("node:fs");
|
|
12
|
+
const index_js_1 = require("../parsers/index.js");
|
|
13
|
+
/** Discover all scannable files in the given paths. */
|
|
14
|
+
async function discoverFiles(paths, ignorePatterns = []) {
|
|
15
|
+
const extensions = new Set((0, index_js_1.getSupportedExtensions)());
|
|
16
|
+
const allFiles = [];
|
|
17
|
+
for (const p of paths) {
|
|
18
|
+
const absPath = (0, node_path_1.resolve)(p);
|
|
19
|
+
const info = await (0, promises_1.stat)(absPath).catch(() => null);
|
|
20
|
+
if (!info)
|
|
21
|
+
continue;
|
|
22
|
+
if (info.isFile()) {
|
|
23
|
+
const ext = getExtension(absPath);
|
|
24
|
+
if (extensions.has(ext))
|
|
25
|
+
allFiles.push(absPath);
|
|
26
|
+
}
|
|
27
|
+
else if (info.isDirectory()) {
|
|
28
|
+
const found = await discoverInDirectory(absPath, extensions, ignorePatterns);
|
|
29
|
+
allFiles.push(...found);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return allFiles;
|
|
33
|
+
}
|
|
34
|
+
/** Get file extension including the dot. */
|
|
35
|
+
function getExtension(filePath) {
|
|
36
|
+
const dot = filePath.lastIndexOf('.');
|
|
37
|
+
return dot === -1 ? '' : filePath.slice(dot);
|
|
38
|
+
}
|
|
39
|
+
/** Discover files within a directory using fast-glob. */
|
|
40
|
+
async function discoverInDirectory(dirPath, extensions, ignorePatterns) {
|
|
41
|
+
const extList = [...extensions].map((e) => e.slice(1));
|
|
42
|
+
const pattern = `**/*.{${extList.join(',')}}`;
|
|
43
|
+
const gitignore = await loadGitignore(dirPath);
|
|
44
|
+
const ig = (0, ignore_1.default)().add(gitignore).add(ignorePatterns);
|
|
45
|
+
const entries = await (0, fast_glob_1.default)(pattern, {
|
|
46
|
+
cwd: dirPath,
|
|
47
|
+
absolute: true,
|
|
48
|
+
ignore: ['**/node_modules/**', '**/dist/**', '**/.git/**', '**/vendor/**'],
|
|
49
|
+
dot: false,
|
|
50
|
+
});
|
|
51
|
+
return filterWithGitignore(entries, dirPath, ig);
|
|
52
|
+
}
|
|
53
|
+
/** Load .gitignore patterns from the target directory. */
|
|
54
|
+
async function loadGitignore(basePath) {
|
|
55
|
+
const gitignorePath = (0, node_path_1.join)(basePath, '.gitignore');
|
|
56
|
+
if (!(0, node_fs_1.existsSync)(gitignorePath))
|
|
57
|
+
return [];
|
|
58
|
+
try {
|
|
59
|
+
const content = await (0, promises_1.readFile)(gitignorePath, 'utf-8');
|
|
60
|
+
return content
|
|
61
|
+
.split('\n')
|
|
62
|
+
.map((line) => line.trim())
|
|
63
|
+
.filter((line) => line && !line.startsWith('#'));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Filter file paths using the ignore instance. */
|
|
70
|
+
function filterWithGitignore(files, basePath, ig) {
|
|
71
|
+
return files.filter((file) => {
|
|
72
|
+
const rel = (0, node_path_1.relative)(basePath, file);
|
|
73
|
+
return !ig.ignores(rel);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/scanner/discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** A file changed in a git diff with its changed line numbers. */
|
|
2
|
+
export interface ChangedFile {
|
|
3
|
+
filePath: string;
|
|
4
|
+
changedLines: Set<number>;
|
|
5
|
+
}
|
|
6
|
+
/** Get files and changed lines from git diff. */
|
|
7
|
+
export declare function getChangedFiles(ref?: string): Promise<ChangedFile[]>;
|
|
8
|
+
//# sourceMappingURL=git-diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-diff.d.ts","sourceRoot":"","sources":["../../src/scanner/git-diff.ts"],"names":[],"mappings":"AAKA,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC3B;AAED,iDAAiD;AACjD,wBAAsB,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAG1E"}
|