grepleaks 1.3.4 → 1.3.7
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/bin/grepleaks.js +85 -10
- package/package.json +1 -1
package/bin/grepleaks.js
CHANGED
|
@@ -136,8 +136,20 @@ async function createZip() {
|
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
// Send scan request to API
|
|
139
|
+
// Send scan request to API (async endpoint with AI analysis)
|
|
140
140
|
async function sendScan(zipPath, apiKey) {
|
|
141
|
+
// Step 1: Submit the scan job
|
|
142
|
+
const jobId = await submitScanJob(zipPath, apiKey);
|
|
143
|
+
|
|
144
|
+
// Step 2: Poll for completion
|
|
145
|
+
logInfo('Processing with AI analysis...');
|
|
146
|
+
const result = await pollForCompletion(jobId, apiKey);
|
|
147
|
+
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Submit scan job to async endpoint
|
|
152
|
+
function submitScanJob(zipPath, apiKey) {
|
|
141
153
|
return new Promise((resolve, reject) => {
|
|
142
154
|
const boundary = '----FormBoundary' + Math.random().toString(36).slice(2);
|
|
143
155
|
const fileContent = fs.readFileSync(zipPath);
|
|
@@ -153,7 +165,7 @@ async function sendScan(zipPath, apiKey) {
|
|
|
153
165
|
const bodyEndBuf = Buffer.from(bodyEnd);
|
|
154
166
|
const body = Buffer.concat([bodyStart, fileContent, bodyEndBuf]);
|
|
155
167
|
|
|
156
|
-
const url = new URL(`${API_URL}/api/v1/scan`);
|
|
168
|
+
const url = new URL(`${API_URL}/api/v1/scan/async`);
|
|
157
169
|
const options = {
|
|
158
170
|
hostname: url.hostname,
|
|
159
171
|
port: url.port || 80,
|
|
@@ -171,7 +183,14 @@ async function sendScan(zipPath, apiKey) {
|
|
|
171
183
|
res.on('data', (chunk) => data += chunk);
|
|
172
184
|
res.on('end', () => {
|
|
173
185
|
try {
|
|
174
|
-
|
|
186
|
+
const response = JSON.parse(data);
|
|
187
|
+
if (response.error) {
|
|
188
|
+
reject(new Error(response.error));
|
|
189
|
+
} else if (response.job_id) {
|
|
190
|
+
resolve(response.job_id);
|
|
191
|
+
} else {
|
|
192
|
+
reject(new Error('No job_id in response'));
|
|
193
|
+
}
|
|
175
194
|
} catch (e) {
|
|
176
195
|
reject(new Error(`Invalid response: ${data}`));
|
|
177
196
|
}
|
|
@@ -184,6 +203,57 @@ async function sendScan(zipPath, apiKey) {
|
|
|
184
203
|
});
|
|
185
204
|
}
|
|
186
205
|
|
|
206
|
+
// Poll for job completion
|
|
207
|
+
function pollForCompletion(jobId, apiKey, maxAttempts = 60) {
|
|
208
|
+
return new Promise((resolve, reject) => {
|
|
209
|
+
let attempts = 0;
|
|
210
|
+
|
|
211
|
+
const checkStatus = () => {
|
|
212
|
+
attempts++;
|
|
213
|
+
|
|
214
|
+
const url = new URL(`${API_URL}/api/v1/scan/${jobId}/status`);
|
|
215
|
+
const options = {
|
|
216
|
+
hostname: url.hostname,
|
|
217
|
+
port: url.port || 80,
|
|
218
|
+
path: url.pathname,
|
|
219
|
+
method: 'GET',
|
|
220
|
+
headers: {
|
|
221
|
+
'X-API-Key': apiKey,
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const req = http.request(options, (res) => {
|
|
226
|
+
let data = '';
|
|
227
|
+
res.on('data', (chunk) => data += chunk);
|
|
228
|
+
res.on('end', () => {
|
|
229
|
+
try {
|
|
230
|
+
const response = JSON.parse(data);
|
|
231
|
+
|
|
232
|
+
if (response.status === 'completed') {
|
|
233
|
+
resolve(response.result || response);
|
|
234
|
+
} else if (response.status === 'failed') {
|
|
235
|
+
reject(new Error(response.error || 'Scan failed'));
|
|
236
|
+
} else if (attempts >= maxAttempts) {
|
|
237
|
+
reject(new Error('Scan timed out'));
|
|
238
|
+
} else {
|
|
239
|
+
// Still processing, wait and retry
|
|
240
|
+
process.stdout.write('.');
|
|
241
|
+
setTimeout(checkStatus, 2000);
|
|
242
|
+
}
|
|
243
|
+
} catch (e) {
|
|
244
|
+
reject(new Error(`Invalid response: ${data}`));
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
req.on('error', (e) => reject(e));
|
|
250
|
+
req.end();
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
checkStatus();
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
187
257
|
// Generate markdown report
|
|
188
258
|
function generateReport(result) {
|
|
189
259
|
const date = new Date().toISOString().split('T')[0];
|
|
@@ -207,13 +277,13 @@ function generateReport(result) {
|
|
|
207
277
|
|
|
208
278
|
`;
|
|
209
279
|
|
|
210
|
-
|
|
280
|
+
const findings = result.vulnerabilities || result.findings || [];
|
|
281
|
+
if (findings.length > 0) {
|
|
211
282
|
report += `## Findings\n\n`;
|
|
212
|
-
|
|
213
|
-
report += `### ${i + 1}. ${finding.
|
|
283
|
+
findings.forEach((finding, i) => {
|
|
284
|
+
report += `### ${i + 1}. ${finding.rule_id || finding.title || 'Finding'}\n\n`;
|
|
214
285
|
report += `- **Severity:** ${finding.severity || 'Unknown'}\n`;
|
|
215
|
-
report += `- **
|
|
216
|
-
if (finding.line) report += `- **Line:** ${finding.line}\n`;
|
|
286
|
+
report += `- **Location:** ${finding.location || finding.file || 'N/A'}\n`;
|
|
217
287
|
if (finding.description) report += `- **Description:** ${finding.description}\n`;
|
|
218
288
|
report += '\n';
|
|
219
289
|
});
|
|
@@ -382,9 +452,14 @@ ${colors.bold}========================================${colors.reset}
|
|
|
382
452
|
${colors.bold}========================================${colors.reset}
|
|
383
453
|
`);
|
|
384
454
|
|
|
385
|
-
// Create report
|
|
455
|
+
// Create report - use AI-generated report from API if available
|
|
386
456
|
if (config.createReport !== false) {
|
|
387
|
-
|
|
457
|
+
let report;
|
|
458
|
+
if (result.report_markdown) {
|
|
459
|
+
report = result.report_markdown;
|
|
460
|
+
} else {
|
|
461
|
+
report = generateReport(result);
|
|
462
|
+
}
|
|
388
463
|
const reportPath = path.join(process.cwd(), 'SECURITY_REPORT.md');
|
|
389
464
|
fs.writeFileSync(reportPath, report);
|
|
390
465
|
logSuccess(`Report saved to SECURITY_REPORT.md`);
|