pqm-cli 1.0.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 +254 -0
- package/bin/pqm.js +6 -0
- package/package.json +31 -0
- package/src/ai/analyzer/collector.js +191 -0
- package/src/ai/analyzer/dependency.js +269 -0
- package/src/ai/analyzer/index.js +234 -0
- package/src/ai/analyzer/quality.js +241 -0
- package/src/ai/analyzer/security.js +302 -0
- package/src/ai/index.js +16 -0
- package/src/ai/providers/bailian.js +121 -0
- package/src/ai/providers/base.js +177 -0
- package/src/ai/providers/deepseek.js +100 -0
- package/src/ai/providers/index.js +100 -0
- package/src/ai/providers/openai.js +100 -0
- package/src/builders/base.js +35 -0
- package/src/builders/rollup.js +47 -0
- package/src/builders/vite.js +47 -0
- package/src/cli.js +41 -0
- package/src/commands/ai.js +317 -0
- package/src/commands/build.js +24 -0
- package/src/commands/commit.js +68 -0
- package/src/commands/config.js +113 -0
- package/src/commands/doctor.js +146 -0
- package/src/commands/init.js +61 -0
- package/src/commands/login.js +37 -0
- package/src/commands/publish.js +250 -0
- package/src/commands/release.js +107 -0
- package/src/commands/scan.js +239 -0
- package/src/commands/status.js +129 -0
- package/src/commands/watch.js +170 -0
- package/src/commands/webhook.js +240 -0
- package/src/config/detector.js +82 -0
- package/src/config/global.js +136 -0
- package/src/config/loader.js +49 -0
- package/src/core/builder.js +88 -0
- package/src/index.js +5 -0
- package/src/logs/build.js +47 -0
- package/src/logs/manager.js +60 -0
- package/src/report/formatter.js +282 -0
- package/src/utils/http.js +130 -0
- package/src/utils/logger.js +24 -0
- package/src/utils/prompt.js +132 -0
- package/src/utils/spinner.js +134 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { Severity } from '../../report/formatter.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default configuration for quality analysis
|
|
5
|
+
*/
|
|
6
|
+
const CONFIG = {
|
|
7
|
+
maxLineLength: 120,
|
|
8
|
+
maxFunctionLength: 50,
|
|
9
|
+
maxComplexity: 15,
|
|
10
|
+
maxParams: 5,
|
|
11
|
+
maxNestingLevel: 4
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Analyze code quality
|
|
16
|
+
* @param {string} code - Code to analyze
|
|
17
|
+
* @param {string} language - Programming language
|
|
18
|
+
* @returns {Array<Object>} Quality issues
|
|
19
|
+
*/
|
|
20
|
+
export function analyzeQualityPatterns(code, language) {
|
|
21
|
+
const issues = [];
|
|
22
|
+
const lines = code.split('\n');
|
|
23
|
+
|
|
24
|
+
// Check line lengths
|
|
25
|
+
lines.forEach((line, index) => {
|
|
26
|
+
// Skip comments and strings for length check
|
|
27
|
+
const trimmed = line.trimEnd();
|
|
28
|
+
if (trimmed.length > CONFIG.maxLineLength) {
|
|
29
|
+
issues.push({
|
|
30
|
+
severity: Severity.LOW,
|
|
31
|
+
type: '行过长',
|
|
32
|
+
line: index + 1,
|
|
33
|
+
message: `行长度 ${trimmed.length} 超过建议值 ${CONFIG.maxLineLength}`,
|
|
34
|
+
suggestion: '将长行拆分为多行,提高可读性'
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Check for TODO/FIXME/HACK
|
|
40
|
+
const todoPatterns = [
|
|
41
|
+
{ pattern: /\/\/\s*TODO:/gi, type: 'TODO' },
|
|
42
|
+
{ pattern: /\/\/\s*FIXME:/gi, type: 'FIXME' },
|
|
43
|
+
{ pattern: /\/\/\s*HACK:/gi, type: 'HACK' },
|
|
44
|
+
{ pattern: /\/\/\s*XXX:/gi, type: 'XXX' },
|
|
45
|
+
{ pattern: /#\s*TODO:/gi, type: 'TODO' },
|
|
46
|
+
{ pattern: /#\s*FIXME:/gi, type: 'FIXME' },
|
|
47
|
+
{ pattern: /\/\*\s*TODO:/gi, type: 'TODO' }
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
lines.forEach((line, index) => {
|
|
51
|
+
for (const { pattern, type } of todoPatterns) {
|
|
52
|
+
if (pattern.test(line)) {
|
|
53
|
+
issues.push({
|
|
54
|
+
severity: Severity.INFO,
|
|
55
|
+
type: `${type} 标记`,
|
|
56
|
+
line: index + 1,
|
|
57
|
+
message: `检测到 ${type} 标记`,
|
|
58
|
+
suggestion: '处理待办事项或移除标记'
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Check for console.log/debug statements
|
|
65
|
+
const debugPatterns = [
|
|
66
|
+
/console\.log\s*\(/g,
|
|
67
|
+
/console\.debug\s*\(/g,
|
|
68
|
+
/console\.warn\s*\(/g,
|
|
69
|
+
/debugger\s*;?/g
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
debugPatterns.forEach(pattern => {
|
|
73
|
+
lines.forEach((line, index) => {
|
|
74
|
+
if (pattern.test(line)) {
|
|
75
|
+
// Skip if it's in a comment
|
|
76
|
+
if (line.trim().startsWith('//') || line.trim().startsWith('*')) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
issues.push({
|
|
81
|
+
severity: Severity.INFO,
|
|
82
|
+
type: '调试代码',
|
|
83
|
+
line: index + 1,
|
|
84
|
+
message: '检测到调试语句',
|
|
85
|
+
suggestion: '生产环境应移除或使用日志框架'
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Check for empty catch blocks
|
|
92
|
+
const emptyCatchPattern = /catch\s*\([^)]*\)\s*\{\s*\}/g;
|
|
93
|
+
let match;
|
|
94
|
+
while ((match = emptyCatchPattern.exec(code)) !== null) {
|
|
95
|
+
const lineNumber = code.substring(0, match.index).split('\n').length;
|
|
96
|
+
issues.push({
|
|
97
|
+
severity: Severity.MEDIUM,
|
|
98
|
+
type: '空 catch 块',
|
|
99
|
+
line: lineNumber,
|
|
100
|
+
message: '检测到空的 catch 块,可能隐藏错误',
|
|
101
|
+
suggestion: '添加错误处理逻辑或日志'
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check for var keyword in JS
|
|
106
|
+
if (language === 'javascript' || language === 'typescript') {
|
|
107
|
+
const varPattern = /\bvar\s+\w+/g;
|
|
108
|
+
lines.forEach((line, index) => {
|
|
109
|
+
if (varPattern.test(line) && !line.includes('//') && !line.trim().startsWith('*')) {
|
|
110
|
+
issues.push({
|
|
111
|
+
severity: Severity.LOW,
|
|
112
|
+
type: 'var 关键字',
|
|
113
|
+
line: index + 1,
|
|
114
|
+
message: '使用 var 声明变量可能导致作用域问题',
|
|
115
|
+
suggestion: '使用 const 或 let 替代'
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check for == instead of ===
|
|
122
|
+
if (language === 'javascript' || language === 'typescript') {
|
|
123
|
+
const looseEqPattern = /[^=!]==[^=]/g;
|
|
124
|
+
lines.forEach((line, index) => {
|
|
125
|
+
// Skip comments
|
|
126
|
+
const commentIndex = line.indexOf('//');
|
|
127
|
+
const codePart = commentIndex >= 0 ? line.substring(0, commentIndex) : line;
|
|
128
|
+
|
|
129
|
+
if (looseEqPattern.test(codePart)) {
|
|
130
|
+
issues.push({
|
|
131
|
+
severity: Severity.LOW,
|
|
132
|
+
type: '宽松相等',
|
|
133
|
+
line: index + 1,
|
|
134
|
+
message: '使用 == 可能导致类型转换问题',
|
|
135
|
+
suggestion: '使用 === 进行严格相等比较'
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Check function length (simplified)
|
|
142
|
+
const functionPattern = /function\s+\w+\s*\([^)]*\)\s*\{((?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*)\}/g;
|
|
143
|
+
while ((match = functionPattern.exec(code)) !== null) {
|
|
144
|
+
const functionBody = match[1];
|
|
145
|
+
const bodyLines = functionBody.split('\n').length;
|
|
146
|
+
const lineNumber = code.substring(0, match.index).split('\n').length;
|
|
147
|
+
|
|
148
|
+
if (bodyLines > CONFIG.maxFunctionLength) {
|
|
149
|
+
issues.push({
|
|
150
|
+
severity: Severity.LOW,
|
|
151
|
+
type: '函数过长',
|
|
152
|
+
line: lineNumber,
|
|
153
|
+
message: `函数体 ${bodyLines} 行,超过建议值 ${CONFIG.maxFunctionLength}`,
|
|
154
|
+
suggestion: '拆分函数,保持单一职责'
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check for deeply nested code (simplified)
|
|
160
|
+
let maxIndent = 0;
|
|
161
|
+
lines.forEach((line, index) => {
|
|
162
|
+
if (line.trim().length === 0) return;
|
|
163
|
+
|
|
164
|
+
const indent = line.search(/\S|$/);
|
|
165
|
+
const nestingLevel = Math.floor(indent / 2);
|
|
166
|
+
|
|
167
|
+
if (nestingLevel > CONFIG.maxNestingLevel) {
|
|
168
|
+
issues.push({
|
|
169
|
+
severity: Severity.LOW,
|
|
170
|
+
type: '嵌套过深',
|
|
171
|
+
line: index + 1,
|
|
172
|
+
message: `嵌套深度 ${nestingLevel},超过建议值 ${CONFIG.maxNestingLevel}`,
|
|
173
|
+
suggestion: '重构代码,减少嵌套层级'
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return issues;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Analyze code quality with AI (optional)
|
|
183
|
+
* @param {Object} provider - AI provider instance
|
|
184
|
+
* @param {string} code - Code to analyze
|
|
185
|
+
* @param {string} language - Programming language
|
|
186
|
+
* @returns {Promise<Array<Object>>} Quality issues
|
|
187
|
+
*/
|
|
188
|
+
export async function analyzeQualityWithAI(provider, code, language) {
|
|
189
|
+
if (!provider) {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const result = await provider.analyzeCode(code, language, { type: 'quality' });
|
|
195
|
+
|
|
196
|
+
if (result.raw) {
|
|
197
|
+
return [{
|
|
198
|
+
severity: Severity.INFO,
|
|
199
|
+
type: 'AI 分析',
|
|
200
|
+
line: 1,
|
|
201
|
+
message: result.analysis,
|
|
202
|
+
suggestion: ''
|
|
203
|
+
}];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return result.issues || [];
|
|
207
|
+
} catch (error) {
|
|
208
|
+
return [{
|
|
209
|
+
severity: Severity.LOW,
|
|
210
|
+
type: 'AI 分析错误',
|
|
211
|
+
line: 1,
|
|
212
|
+
message: `AI 分析失败: ${error.message}`,
|
|
213
|
+
suggestion: '请检查网络连接和 API 配置'
|
|
214
|
+
}];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Run quality analysis
|
|
220
|
+
* @param {string} code - Code to analyze
|
|
221
|
+
* @param {string} language - Programming language
|
|
222
|
+
* @param {Object} options - Analysis options
|
|
223
|
+
* @returns {Array<Object>} Quality issues
|
|
224
|
+
*/
|
|
225
|
+
export async function runQualityAnalysis(code, language, options = {}) {
|
|
226
|
+
const patternIssues = analyzeQualityPatterns(code, language);
|
|
227
|
+
|
|
228
|
+
let aiIssues = [];
|
|
229
|
+
if (options.useAI && options.provider) {
|
|
230
|
+
aiIssues = await analyzeQualityWithAI(options.provider, code, language);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return [...patternIssues, ...aiIssues];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export default {
|
|
237
|
+
analyzeQualityPatterns,
|
|
238
|
+
analyzeQualityWithAI,
|
|
239
|
+
runQualityAnalysis,
|
|
240
|
+
CONFIG
|
|
241
|
+
};
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { Severity } from '../../report/formatter.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Security vulnerability patterns
|
|
5
|
+
*/
|
|
6
|
+
const PATTERNS = {
|
|
7
|
+
// XSS vulnerabilities
|
|
8
|
+
xss: [
|
|
9
|
+
{
|
|
10
|
+
pattern: /innerHTML\s*=\s*[^;]*\+/gi,
|
|
11
|
+
message: '使用 innerHTML 拼接内容可能导致 XSS 攻击',
|
|
12
|
+
suggestion: '使用 textContent 或创建 DOM 节点来设置内容'
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
pattern: /document\.write\s*\(/gi,
|
|
16
|
+
message: '使用 document.write 可能导致 XSS 攻击',
|
|
17
|
+
suggestion: '使用 DOM 操作方法替代 document.write'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
pattern: /\.html\s*\(\s*[^)]*\+/gi,
|
|
21
|
+
message: 'jQuery .html() 拼接内容可能导致 XSS 攻击',
|
|
22
|
+
suggestion: '使用 .text() 或对内容进行转义处理'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
pattern: /dangerouslySetInnerHTML/gi,
|
|
26
|
+
message: 'React dangerouslySetInnerHTML 可能导致 XSS 攻击',
|
|
27
|
+
suggestion: '避免使用 dangerouslySetInnerHTML,或确保内容已转义'
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
|
|
31
|
+
// SQL injection
|
|
32
|
+
sqlInjection: [
|
|
33
|
+
{
|
|
34
|
+
pattern: /query\s*\(\s*[`'"]\s*SELECT.*\$\{.*\}/gis,
|
|
35
|
+
message: 'SQL 查询拼接变量可能导致 SQL 注入',
|
|
36
|
+
suggestion: '使用参数化查询或 ORM'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
pattern: /\.query\s*\(\s*[`'"].*\+/gi,
|
|
40
|
+
message: 'SQL 查询拼接字符串可能导致 SQL 注入',
|
|
41
|
+
suggestion: '使用参数化查询,避免直接拼接用户输入'
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
pattern: /execute\s*\(\s*[`'"].*\+/gi,
|
|
45
|
+
message: 'SQL 执行拼接字符串可能导致 SQL 注入',
|
|
46
|
+
suggestion: '使用参数化查询'
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
|
|
50
|
+
// Command injection
|
|
51
|
+
commandInjection: [
|
|
52
|
+
{
|
|
53
|
+
pattern: /exec\s*\(\s*[`'"].*\+/gi,
|
|
54
|
+
message: '命令执行拼接字符串可能导致命令注入',
|
|
55
|
+
suggestion: '使用 child_process.spawn 并传递参数数组'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
pattern: /execSync\s*\(\s*[`'"].*\+/gi,
|
|
59
|
+
message: '同步命令执行拼接字符串可能导致命令注入',
|
|
60
|
+
suggestion: '使用 child_process.spawnSync 并传递参数数组'
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
pattern: /spawn\s*\([^)]*\+/gi,
|
|
64
|
+
message: 'spawn 拼接命令可能导致命令注入',
|
|
65
|
+
suggestion: '分离命令和参数,使用数组形式传递'
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
|
|
69
|
+
// Dangerous eval
|
|
70
|
+
evalUsage: [
|
|
71
|
+
{
|
|
72
|
+
pattern: /\beval\s*\(/gi,
|
|
73
|
+
message: '使用 eval 可能执行恶意代码',
|
|
74
|
+
suggestion: '避免使用 eval,使用 JSON.parse 解析 JSON'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
pattern: /new\s+Function\s*\(/gi,
|
|
78
|
+
message: '使用 new Function 可能执行恶意代码',
|
|
79
|
+
suggestion: '避免动态创建函数'
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
pattern: /setTimeout\s*\(\s*[`'"]/gi,
|
|
83
|
+
message: 'setTimeout 传递字符串参数可能执行恶意代码',
|
|
84
|
+
suggestion: '传递函数引用而非字符串'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
pattern: /setInterval\s*\(\s*[`'"]/gi,
|
|
88
|
+
message: 'setInterval 传递字符串参数可能执行恶意代码',
|
|
89
|
+
suggestion: '传递函数引用而非字符串'
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
|
|
93
|
+
// Hardcoded secrets
|
|
94
|
+
hardcodedSecrets: [
|
|
95
|
+
{
|
|
96
|
+
pattern: /password\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
97
|
+
message: '检测到硬编码密码',
|
|
98
|
+
suggestion: '从环境变量或配置文件读取密码'
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
pattern: /api[_-]?key\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
102
|
+
message: '检测到硬编码 API Key',
|
|
103
|
+
suggestion: '从环境变量读取 API Key'
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
pattern: /secret[_-]?key\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
107
|
+
message: '检测到硬编码密钥',
|
|
108
|
+
suggestion: '从环境变量或密钥管理服务读取'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
pattern: /token\s*[:=]\s*['"]\w{16,}['"]/gi,
|
|
112
|
+
message: '检测到硬编码 Token',
|
|
113
|
+
suggestion: '从环境变量读取 Token'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
pattern: /private[_-]?key\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
117
|
+
message: '检测到硬编码私钥',
|
|
118
|
+
suggestion: '从安全存储读取私钥'
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
|
|
122
|
+
// Path traversal
|
|
123
|
+
pathTraversal: [
|
|
124
|
+
{
|
|
125
|
+
pattern: /path\.join\s*\([^)]*\+/gi,
|
|
126
|
+
message: '路径拼接用户输入可能导致路径遍历攻击',
|
|
127
|
+
suggestion: '验证和清理用户输入,使用 path.resolve'
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
pattern: /\.+\//gi,
|
|
131
|
+
message: '检测到可能的路径遍历模式',
|
|
132
|
+
suggestion: '验证路径,确保不包含 ../ 等危险模式'
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
|
|
136
|
+
// Insecure deserialization
|
|
137
|
+
insecureDeserialization: [
|
|
138
|
+
{
|
|
139
|
+
pattern: /yaml\.load\s*\(/gi,
|
|
140
|
+
message: 'yaml.load 可能导致不安全的反序列化',
|
|
141
|
+
suggestion: '使用 yaml.safeLoad 并限制对象类型'
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
pattern: /pickle\.loads?\s*\(/gi,
|
|
145
|
+
message: 'pickle 可能导致不安全的反序列化',
|
|
146
|
+
suggestion: '避免 pickle,使用 JSON 等 safer 格式'
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Analyze code for security vulnerabilities
|
|
153
|
+
* @param {string} code - Code to analyze
|
|
154
|
+
* @param {string} language - Programming language
|
|
155
|
+
* @returns {Array<Object>} List of security issues
|
|
156
|
+
*/
|
|
157
|
+
export function analyzeSecurityPatterns(code, language) {
|
|
158
|
+
const issues = [];
|
|
159
|
+
const lines = code.split('\n');
|
|
160
|
+
|
|
161
|
+
for (const [type, patterns] of Object.entries(PATTERNS)) {
|
|
162
|
+
for (const { pattern, message, suggestion } of patterns) {
|
|
163
|
+
let match;
|
|
164
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
165
|
+
|
|
166
|
+
while ((match = regex.exec(code)) !== null) {
|
|
167
|
+
// Line number
|
|
168
|
+
const lineNumber = code.substring(0, match.index).split('\n').length;
|
|
169
|
+
|
|
170
|
+
// Determine severity
|
|
171
|
+
const severity = getSeverity(type);
|
|
172
|
+
|
|
173
|
+
issues.push({
|
|
174
|
+
severity,
|
|
175
|
+
type: getTypeName(type),
|
|
176
|
+
line: lineNumber,
|
|
177
|
+
message,
|
|
178
|
+
suggestion,
|
|
179
|
+
match: match[0].substring(0, 50)
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return issues;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get severity based on vulnerability type
|
|
190
|
+
* @param {string} type - Vulnerability type
|
|
191
|
+
* @returns {string} Severity level
|
|
192
|
+
*/
|
|
193
|
+
function getSeverity(type) {
|
|
194
|
+
const critical = ['evalUsage', 'commandInjection', 'sqlInjection'];
|
|
195
|
+
const high = ['xss', 'hardcodedSecrets'];
|
|
196
|
+
const medium = ['pathTraversal', 'insecureDeserialization'];
|
|
197
|
+
|
|
198
|
+
if (critical.includes(type)) return Severity.CRITICAL;
|
|
199
|
+
if (high.includes(type)) return Severity.HIGH;
|
|
200
|
+
if (medium.includes(type)) return Severity.MEDIUM;
|
|
201
|
+
return Severity.LOW;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get human readable type name
|
|
206
|
+
* @param {string} type - Internal type name
|
|
207
|
+
* @returns {string} Human readable name
|
|
208
|
+
*/
|
|
209
|
+
function getTypeName(type) {
|
|
210
|
+
const names = {
|
|
211
|
+
xss: 'XSS 跨站脚本',
|
|
212
|
+
sqlInjection: 'SQL 注入',
|
|
213
|
+
commandInjection: '命令注入',
|
|
214
|
+
evalUsage: '危险 eval',
|
|
215
|
+
hardcodedSecrets: '硬编码密钥',
|
|
216
|
+
pathTraversal: '路径遍历',
|
|
217
|
+
insecureDeserialization: '不安全反序列化'
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return names[type] || type;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Analyze security with AI (optional)
|
|
225
|
+
* @param {Object} provider - AI provider instance
|
|
226
|
+
* @param {string} code - Code to analyze
|
|
227
|
+
* @param {string} language - Programming language
|
|
228
|
+
* @returns {Promise<Array<Object>>} Security issues
|
|
229
|
+
*/
|
|
230
|
+
export async function analyzeWithAI(provider, code, language) {
|
|
231
|
+
if (!provider) {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const result = await provider.analyzeCode(code, language, { type: 'security' });
|
|
237
|
+
|
|
238
|
+
if (result.raw) {
|
|
239
|
+
// If AI returned raw text, try to extract issues
|
|
240
|
+
return [{
|
|
241
|
+
severity: Severity.INFO,
|
|
242
|
+
type: 'AI 分析',
|
|
243
|
+
line: 1,
|
|
244
|
+
message: result.analysis,
|
|
245
|
+
suggestion: ''
|
|
246
|
+
}];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return result.issues || [];
|
|
250
|
+
} catch (error) {
|
|
251
|
+
return [{
|
|
252
|
+
severity: Severity.LOW,
|
|
253
|
+
type: 'AI 分析错误',
|
|
254
|
+
line: 1,
|
|
255
|
+
message: `AI 分析失败: ${error.message}`,
|
|
256
|
+
suggestion: '请检查网络连接和 API 配置'
|
|
257
|
+
}];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Run security analysis
|
|
263
|
+
* @param {string} code - Code to analyze
|
|
264
|
+
* @param {string} language - Programming language
|
|
265
|
+
* @param {Object} options - Analysis options
|
|
266
|
+
* @returns {Array<Object>} Security issues
|
|
267
|
+
*/
|
|
268
|
+
export async function runSecurityAnalysis(code, language, options = {}) {
|
|
269
|
+
// First run pattern-based analysis
|
|
270
|
+
const patternIssues = analyzeSecurityPatterns(code, language);
|
|
271
|
+
|
|
272
|
+
// Optionally run AI analysis
|
|
273
|
+
let aiIssues = [];
|
|
274
|
+
if (options.useAI && options.provider) {
|
|
275
|
+
aiIssues = await analyzeWithAI(options.provider, code, language);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Combine and deduplicate
|
|
279
|
+
const allIssues = [...patternIssues, ...aiIssues];
|
|
280
|
+
|
|
281
|
+
// Sort by severity
|
|
282
|
+
const severityOrder = {
|
|
283
|
+
[Severity.CRITICAL]: 0,
|
|
284
|
+
[Severity.HIGH]: 1,
|
|
285
|
+
[Severity.MEDIUM]: 2,
|
|
286
|
+
[Severity.LOW]: 3,
|
|
287
|
+
[Severity.INFO]: 4
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
allIssues.sort((a, b) => {
|
|
291
|
+
return (severityOrder[a.severity] || 5) - (severityOrder[b.severity] || 5);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
return allIssues;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export default {
|
|
298
|
+
analyzeSecurityPatterns,
|
|
299
|
+
analyzeWithAI,
|
|
300
|
+
runSecurityAnalysis,
|
|
301
|
+
PATTERNS
|
|
302
|
+
};
|
package/src/ai/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createProvider, getSupportedProviders } from './providers/index.js';
|
|
2
|
+
import { createAnalyzer, AnalysisType } from './analyzer/index.js';
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
createProvider,
|
|
6
|
+
getSupportedProviders,
|
|
7
|
+
createAnalyzer,
|
|
8
|
+
AnalysisType
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
createProvider,
|
|
13
|
+
getSupportedProviders,
|
|
14
|
+
createAnalyzer,
|
|
15
|
+
AnalysisType
|
|
16
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { BaseAIProvider } from './base.js';
|
|
2
|
+
import { post } from '../../utils/http.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Alibaba Cloud Bailian (阿里云百炼) Provider
|
|
6
|
+
* API Documentation: https://help.aliyun.com/document_detail/2712195.html
|
|
7
|
+
*/
|
|
8
|
+
export class BailianProvider extends BaseAIProvider {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
super(config);
|
|
11
|
+
this.endpoint = config.endpoint || 'https://dashscope.aliyuncs.com/api/v1';
|
|
12
|
+
this.model = config.model || 'qwen-turbo';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getDefaultModel() {
|
|
16
|
+
return 'qwen-turbo';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getDefaultEndpoint() {
|
|
20
|
+
return 'https://dashscope.aliyuncs.com/api/v1';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async chat(prompt, options = {}) {
|
|
24
|
+
const url = `${this.endpoint}/services/aigc/text-generation/generation`;
|
|
25
|
+
|
|
26
|
+
const body = {
|
|
27
|
+
model: this.model,
|
|
28
|
+
input: {
|
|
29
|
+
messages: [
|
|
30
|
+
{
|
|
31
|
+
role: 'user',
|
|
32
|
+
content: prompt
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
parameters: {
|
|
37
|
+
max_tokens: options.maxTokens || 4096,
|
|
38
|
+
temperature: options.temperature || 0.7,
|
|
39
|
+
result_format: 'message'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const headers = {
|
|
44
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
45
|
+
'Content-Type': 'application/json'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const response = await post(url, body, headers, 60000);
|
|
50
|
+
|
|
51
|
+
// Handle different response formats
|
|
52
|
+
if (response.output?.text) {
|
|
53
|
+
return response.output.text;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (response.output?.choices?.[0]?.message?.content) {
|
|
57
|
+
return response.output.choices[0].message.content;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (response.choices?.[0]?.message?.content) {
|
|
61
|
+
return response.choices[0].message.content;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
throw new Error('Unexpected response format from Bailian API');
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new Error(`Bailian API error: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async validateConfig() {
|
|
71
|
+
if (!this.apiKey) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Basic API key format check for Bailian
|
|
76
|
+
if (!this.apiKey.startsWith('sk-')) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
// Try a minimal request to validate
|
|
82
|
+
const url = `${this.endpoint}/services/aigc/text-generation/generation`;
|
|
83
|
+
const body = {
|
|
84
|
+
model: this.model,
|
|
85
|
+
input: {
|
|
86
|
+
messages: [{ role: 'user', content: 'Hi' }]
|
|
87
|
+
},
|
|
88
|
+
parameters: {
|
|
89
|
+
max_tokens: 10,
|
|
90
|
+
result_format: 'message'
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const headers = {
|
|
95
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
96
|
+
'Content-Type': 'application/json'
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
await post(url, body, headers, 10000);
|
|
100
|
+
return true;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
// If we get a 400 or 401, the key format is wrong
|
|
103
|
+
// If we get other errors, the endpoint might be working but other issues
|
|
104
|
+
return !error.message.includes('HTTP 401') && !error.message.includes('HTTP 403');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getInfo() {
|
|
109
|
+
return {
|
|
110
|
+
name: 'bailian',
|
|
111
|
+
displayName: '阿里云百炼',
|
|
112
|
+
model: this.model,
|
|
113
|
+
models: ['qwen-turbo', 'qwen-plus', 'qwen-max', 'qwen-max-longcontext'],
|
|
114
|
+
endpoint: this.endpoint,
|
|
115
|
+
configured: !!this.apiKey,
|
|
116
|
+
free: true // 百炼有免费额度
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default BailianProvider;
|