@cencori/scan 0.4.4 → 0.4.6
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/cli.js +212 -83
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +213 -84
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -856,17 +856,29 @@ async function generateFixes(issues, fileContents) {
|
|
|
856
856
|
"Authorization": `Bearer ${apiKey}`
|
|
857
857
|
},
|
|
858
858
|
body: JSON.stringify({
|
|
859
|
-
model: "
|
|
859
|
+
model: "gpt-4o-mini",
|
|
860
|
+
// Use reliable model
|
|
860
861
|
messages: [
|
|
861
862
|
{
|
|
862
863
|
role: "system",
|
|
863
|
-
content: `You are a security engineer. Generate secure code fixes.
|
|
864
|
+
content: `You are a security engineer fixing code vulnerabilities. Generate secure code fixes.
|
|
865
|
+
|
|
866
|
+
IMPORTANT: Respond ONLY with valid JSON in this exact format:
|
|
867
|
+
{"fixedCode": "the complete fixed code snippet", "explanation": "brief explanation of what was changed"}
|
|
868
|
+
|
|
869
|
+
Rules:
|
|
870
|
+
- For hardcoded secrets: Replace with environment variables (process.env.VAR_NAME)
|
|
871
|
+
- For XSS vulnerabilities: Add proper escaping/sanitization
|
|
872
|
+
- For SQL injection: Use parameterized queries
|
|
873
|
+
- For exposed routes: Add authentication middleware
|
|
874
|
+
- Keep the same code structure, only fix the security issue`
|
|
864
875
|
},
|
|
865
876
|
{
|
|
866
877
|
role: "user",
|
|
867
878
|
content: `Fix this security issue:
|
|
868
879
|
Type: ${issue.type}
|
|
869
880
|
Name: ${issue.name}
|
|
881
|
+
Severity: ${issue.severity}
|
|
870
882
|
File: ${issue.file}:${issue.line}
|
|
871
883
|
|
|
872
884
|
Code to fix:
|
|
@@ -874,56 +886,87 @@ Code to fix:
|
|
|
874
886
|
${codeSnippet}
|
|
875
887
|
\`\`\`
|
|
876
888
|
|
|
877
|
-
|
|
889
|
+
Respond with JSON only.`
|
|
878
890
|
}
|
|
879
891
|
],
|
|
880
892
|
temperature: 0,
|
|
881
|
-
max_tokens:
|
|
893
|
+
max_tokens: 1e3
|
|
882
894
|
})
|
|
883
895
|
});
|
|
884
896
|
if (!response.ok) {
|
|
885
|
-
|
|
897
|
+
const errorText = await response.text();
|
|
898
|
+
console.error(`[AI] API error for ${issue.file}:${issue.line}: ${response.status} - ${errorText}`);
|
|
899
|
+
results.push({
|
|
900
|
+
issue,
|
|
901
|
+
originalCode: codeSnippet,
|
|
902
|
+
fixedCode: codeSnippet,
|
|
903
|
+
// Same as original = no fix
|
|
904
|
+
explanation: `API error: ${response.status}`,
|
|
905
|
+
applied: false
|
|
906
|
+
});
|
|
907
|
+
continue;
|
|
886
908
|
}
|
|
887
909
|
const data = await response.json();
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
910
|
+
if (data.error) {
|
|
911
|
+
console.error(`[AI] API returned error: ${data.error.message}`);
|
|
912
|
+
results.push({
|
|
913
|
+
issue,
|
|
914
|
+
originalCode: codeSnippet,
|
|
915
|
+
fixedCode: codeSnippet,
|
|
916
|
+
explanation: `AI error: ${data.error.message}`,
|
|
917
|
+
applied: false
|
|
918
|
+
});
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
const content_response = data.choices[0]?.message?.content || "";
|
|
922
|
+
let jsonStr = content_response;
|
|
923
|
+
const jsonMatch = content_response.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
924
|
+
if (jsonMatch) {
|
|
925
|
+
jsonStr = jsonMatch[1].trim();
|
|
926
|
+
}
|
|
927
|
+
try {
|
|
928
|
+
const parsed = JSON.parse(jsonStr);
|
|
929
|
+
const fixedCode = parsed.fixedCode || parsed.fixed_code || "";
|
|
930
|
+
if (fixedCode && fixedCode !== codeSnippet) {
|
|
931
|
+
results.push({
|
|
932
|
+
issue,
|
|
933
|
+
originalCode: codeSnippet,
|
|
934
|
+
fixedCode,
|
|
935
|
+
explanation: parsed.explanation || "Security fix applied",
|
|
936
|
+
applied: false
|
|
937
|
+
});
|
|
938
|
+
} else {
|
|
939
|
+
results.push({
|
|
940
|
+
issue,
|
|
941
|
+
originalCode: codeSnippet,
|
|
942
|
+
fixedCode: codeSnippet,
|
|
943
|
+
explanation: "AI could not generate a fix for this issue",
|
|
944
|
+
applied: false
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
} catch (parseError) {
|
|
948
|
+
console.error(`[AI] JSON parse error for ${issue.file}:${issue.line}:`, parseError);
|
|
949
|
+
results.push({
|
|
950
|
+
issue,
|
|
951
|
+
originalCode: codeSnippet,
|
|
952
|
+
fixedCode: codeSnippet,
|
|
953
|
+
explanation: "Failed to parse AI response",
|
|
954
|
+
applied: false
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
} catch (error) {
|
|
958
|
+
console.error(`[AI] Request failed for ${issue.file}:${issue.line}:`, error);
|
|
898
959
|
results.push({
|
|
899
960
|
issue,
|
|
900
961
|
originalCode: codeSnippet,
|
|
901
962
|
fixedCode: codeSnippet,
|
|
902
|
-
explanation:
|
|
963
|
+
explanation: `Request failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
903
964
|
applied: false
|
|
904
965
|
});
|
|
905
966
|
}
|
|
906
967
|
}
|
|
907
968
|
return results;
|
|
908
969
|
}
|
|
909
|
-
async function applyFixes(fixes, fileContents) {
|
|
910
|
-
for (const fix of fixes) {
|
|
911
|
-
if (fix.fixedCode === fix.originalCode) {
|
|
912
|
-
continue;
|
|
913
|
-
}
|
|
914
|
-
const content = fileContents.get(fix.issue.file);
|
|
915
|
-
if (!content) {
|
|
916
|
-
continue;
|
|
917
|
-
}
|
|
918
|
-
const newContent = content.replace(fix.originalCode, fix.fixedCode);
|
|
919
|
-
if (newContent !== content) {
|
|
920
|
-
const filePath = path2.resolve(fix.issue.file);
|
|
921
|
-
fs2.writeFileSync(filePath, newContent, "utf-8");
|
|
922
|
-
fix.applied = true;
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
return fixes;
|
|
926
|
-
}
|
|
927
970
|
|
|
928
971
|
// src/telemetry.ts
|
|
929
972
|
var TELEMETRY_URL = "https://api.cencori.com/v1/telemetry/scan";
|
|
@@ -1176,7 +1219,7 @@ No commits found in the specified period.
|
|
|
1176
1219
|
// src/cli.ts
|
|
1177
1220
|
var fs3 = __toESM(require("fs"));
|
|
1178
1221
|
var path3 = __toESM(require("path"));
|
|
1179
|
-
var VERSION = "0.4.
|
|
1222
|
+
var VERSION = "0.4.6";
|
|
1180
1223
|
var scoreStyles = {
|
|
1181
1224
|
A: { color: import_chalk.default.green },
|
|
1182
1225
|
B: { color: import_chalk.default.blue },
|
|
@@ -1411,57 +1454,137 @@ async function handleAutoFix(result, targetPath) {
|
|
|
1411
1454
|
fileContents
|
|
1412
1455
|
);
|
|
1413
1456
|
fixSpinner.succeed(`Generated ${fixes.length} fixes`);
|
|
1414
|
-
const applySpinner = (0, import_ora.default)({
|
|
1415
|
-
text: "Applying fixes...",
|
|
1416
|
-
color: "cyan"
|
|
1417
|
-
}).start();
|
|
1418
|
-
const appliedFixes = await applyFixes(fixes, fileContents);
|
|
1419
|
-
const appliedCount = appliedFixes.filter((f) => f.applied).length;
|
|
1420
|
-
applySpinner.succeed(`Applied ${appliedCount}/${fixes.length} fixes`);
|
|
1421
1457
|
console.log();
|
|
1422
|
-
const
|
|
1423
|
-
const
|
|
1424
|
-
if (
|
|
1425
|
-
console.log(` ${
|
|
1426
|
-
for (const fix of applied) {
|
|
1427
|
-
console.log(import_chalk.default.green(` \u2714 ${fix.issue.file}:${fix.issue.line}`));
|
|
1428
|
-
console.log(import_chalk.default.gray(` ${fix.explanation}`));
|
|
1429
|
-
}
|
|
1458
|
+
const actualFixes = fixes.filter((f) => f.fixedCode !== f.originalCode);
|
|
1459
|
+
const failedFixes = fixes.filter((f) => f.fixedCode === f.originalCode);
|
|
1460
|
+
if (failedFixes.length > 0) {
|
|
1461
|
+
console.log(import_chalk.default.gray(` Note: ${failedFixes.length} issues could not be auto-fixed (saved for manual review)`));
|
|
1430
1462
|
console.log();
|
|
1431
1463
|
}
|
|
1432
|
-
if (
|
|
1433
|
-
console.log(
|
|
1464
|
+
if (actualFixes.length === 0) {
|
|
1465
|
+
console.log(import_chalk.default.yellow(" No fixes could be generated by AI."));
|
|
1466
|
+
console.log(import_chalk.default.gray(" This may be due to API issues or complex code patterns."));
|
|
1467
|
+
const fixesFile = ".cencori-fixes.json";
|
|
1468
|
+
const fixesData = {
|
|
1469
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1470
|
+
total_fixes: failedFixes.length,
|
|
1471
|
+
note: "AI could not generate automatic fixes for these issues",
|
|
1472
|
+
fixes: failedFixes.map((f) => ({
|
|
1473
|
+
file: f.issue.file,
|
|
1474
|
+
line: f.issue.line,
|
|
1475
|
+
issue_type: f.issue.type,
|
|
1476
|
+
issue_name: f.issue.name,
|
|
1477
|
+
severity: f.issue.severity,
|
|
1478
|
+
original_code: f.originalCode,
|
|
1479
|
+
explanation: f.explanation
|
|
1480
|
+
}))
|
|
1481
|
+
};
|
|
1482
|
+
fs3.writeFileSync(fixesFile, JSON.stringify(fixesData, null, 2));
|
|
1483
|
+
console.log(import_chalk.default.cyan(` Issues saved to ${import_chalk.default.bold(fixesFile)} for manual review.`));
|
|
1484
|
+
console.log();
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
const acceptedFixes = [];
|
|
1488
|
+
const skippedFixes = [];
|
|
1489
|
+
let applyAll = false;
|
|
1490
|
+
let skipRest = false;
|
|
1491
|
+
for (let i = 0; i < actualFixes.length; i++) {
|
|
1492
|
+
const fix = actualFixes[i];
|
|
1493
|
+
if (skipRest) {
|
|
1494
|
+
skippedFixes.push(fix);
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
if (applyAll) {
|
|
1498
|
+
acceptedFixes.push(fix);
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
console.log(import_chalk.default.cyan(` \u2500\u2500\u2500 Fix ${i + 1}/${fixes.length}: ${fix.issue.file}:${fix.issue.line} \u2500\u2500\u2500`));
|
|
1502
|
+
console.log(import_chalk.default.gray(` Issue: ${fix.issue.name} (${fix.issue.severity})`));
|
|
1503
|
+
console.log();
|
|
1504
|
+
const origLines = fix.originalCode.split("\n");
|
|
1505
|
+
console.log(import_chalk.default.red(" - Original:"));
|
|
1506
|
+
origLines.slice(0, 8).forEach((line) => console.log(import_chalk.default.red(` ${line}`)));
|
|
1507
|
+
if (origLines.length > 8) {
|
|
1508
|
+
console.log(import_chalk.default.gray(` ... (${origLines.length - 8} more lines)`));
|
|
1509
|
+
}
|
|
1434
1510
|
console.log();
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
console.log();
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1511
|
+
const fixLines = fix.fixedCode.split("\n");
|
|
1512
|
+
console.log(import_chalk.default.green(" + Suggested fix:"));
|
|
1513
|
+
fixLines.slice(0, 8).forEach((line) => console.log(import_chalk.default.green(` ${line}`)));
|
|
1514
|
+
if (fixLines.length > 8) {
|
|
1515
|
+
console.log(import_chalk.default.gray(` ... (${fixLines.length - 8} more lines)`));
|
|
1516
|
+
}
|
|
1517
|
+
console.log();
|
|
1518
|
+
console.log(import_chalk.default.gray(` Explanation: ${fix.explanation}`));
|
|
1519
|
+
console.log();
|
|
1520
|
+
const action = await (0, import_prompts.select)({
|
|
1521
|
+
message: "Apply this fix?",
|
|
1522
|
+
choices: [
|
|
1523
|
+
{ name: "Yes - apply this fix", value: "y" },
|
|
1524
|
+
{ name: "No - skip this fix", value: "n" },
|
|
1525
|
+
{ name: "All - apply all remaining fixes", value: "a" },
|
|
1526
|
+
{ name: "Skip rest - save remaining to file", value: "s" },
|
|
1527
|
+
{ name: "Quit - stop reviewing", value: "q" }
|
|
1528
|
+
]
|
|
1529
|
+
});
|
|
1530
|
+
if (action === "y") {
|
|
1531
|
+
acceptedFixes.push(fix);
|
|
1532
|
+
console.log(import_chalk.default.green(" \u2714 Fix accepted"));
|
|
1533
|
+
} else if (action === "n") {
|
|
1534
|
+
skippedFixes.push(fix);
|
|
1535
|
+
console.log(import_chalk.default.yellow(" \u2298 Fix skipped"));
|
|
1536
|
+
} else if (action === "a") {
|
|
1537
|
+
applyAll = true;
|
|
1538
|
+
acceptedFixes.push(fix);
|
|
1539
|
+
for (let j = i + 1; j < actualFixes.length; j++) {
|
|
1540
|
+
acceptedFixes.push(actualFixes[j]);
|
|
1445
1541
|
}
|
|
1446
|
-
console.log();
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1542
|
+
console.log(import_chalk.default.green(` \u2714 Applying all ${actualFixes.length - i} remaining fixes`));
|
|
1543
|
+
break;
|
|
1544
|
+
} else if (action === "s") {
|
|
1545
|
+
skipRest = true;
|
|
1546
|
+
skippedFixes.push(fix);
|
|
1547
|
+
for (let j = i + 1; j < actualFixes.length; j++) {
|
|
1548
|
+
skippedFixes.push(actualFixes[j]);
|
|
1452
1549
|
}
|
|
1453
|
-
console.log(import_chalk.default.
|
|
1454
|
-
|
|
1550
|
+
console.log(import_chalk.default.yellow(` \u2298 Skipping ${actualFixes.length - i} remaining fixes`));
|
|
1551
|
+
break;
|
|
1552
|
+
} else if (action === "q") {
|
|
1553
|
+
skippedFixes.push(fix);
|
|
1554
|
+
for (let j = i + 1; j < actualFixes.length; j++) {
|
|
1555
|
+
skippedFixes.push(actualFixes[j]);
|
|
1556
|
+
}
|
|
1557
|
+
console.log(import_chalk.default.gray(" Stopped reviewing"));
|
|
1558
|
+
break;
|
|
1455
1559
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1560
|
+
console.log();
|
|
1561
|
+
}
|
|
1562
|
+
if (acceptedFixes.length > 0) {
|
|
1563
|
+
console.log();
|
|
1564
|
+
const applySpinner = (0, import_ora.default)({
|
|
1565
|
+
text: `Applying ${acceptedFixes.length} fixes...`,
|
|
1566
|
+
color: "cyan"
|
|
1567
|
+
}).start();
|
|
1568
|
+
let appliedCount = 0;
|
|
1569
|
+
for (const fix of acceptedFixes) {
|
|
1570
|
+
const content = fileContents.get(fix.issue.file);
|
|
1571
|
+
if (!content) continue;
|
|
1572
|
+
const newContent = content.replace(fix.originalCode, fix.fixedCode);
|
|
1573
|
+
if (newContent !== content) {
|
|
1574
|
+
const filePath = path3.resolve(targetPath, fix.issue.file);
|
|
1575
|
+
fs3.writeFileSync(filePath, newContent, "utf-8");
|
|
1576
|
+
fileContents.set(fix.issue.file, newContent);
|
|
1577
|
+
appliedCount++;
|
|
1578
|
+
}
|
|
1459
1579
|
}
|
|
1580
|
+
applySpinner.succeed(`Applied ${appliedCount}/${acceptedFixes.length} fixes to your codebase`);
|
|
1581
|
+
}
|
|
1582
|
+
if (skippedFixes.length > 0) {
|
|
1460
1583
|
const fixesFile = ".cencori-fixes.json";
|
|
1461
1584
|
const fixesData = {
|
|
1462
1585
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1463
|
-
total_fixes:
|
|
1464
|
-
fixes:
|
|
1586
|
+
total_fixes: skippedFixes.length,
|
|
1587
|
+
fixes: skippedFixes.map((f) => ({
|
|
1465
1588
|
file: f.issue.file,
|
|
1466
1589
|
line: f.issue.line,
|
|
1467
1590
|
issue_type: f.issue.type,
|
|
@@ -1473,16 +1596,22 @@ async function handleAutoFix(result, targetPath) {
|
|
|
1473
1596
|
}))
|
|
1474
1597
|
};
|
|
1475
1598
|
fs3.writeFileSync(fixesFile, JSON.stringify(fixesData, null, 2));
|
|
1476
|
-
console.log(import_chalk.default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1477
|
-
console.log();
|
|
1478
|
-
console.log(` ${import_chalk.default.bold("Next steps:")}`);
|
|
1479
|
-
console.log(import_chalk.default.cyan(` 1. Review fixes in ${import_chalk.default.bold(fixesFile)}`));
|
|
1480
|
-
console.log(import_chalk.default.cyan(` 2. Apply fixes manually to your codebase`));
|
|
1481
|
-
console.log(import_chalk.default.cyan(` 3. Run ${import_chalk.default.bold("npx @cencori/scan")} again to verify`));
|
|
1482
1599
|
console.log();
|
|
1600
|
+
console.log(import_chalk.default.yellow(` ${skippedFixes.length} skipped fixes saved to ${import_chalk.default.bold(fixesFile)}`));
|
|
1601
|
+
}
|
|
1602
|
+
console.log();
|
|
1603
|
+
console.log(import_chalk.default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1604
|
+
console.log();
|
|
1605
|
+
console.log(` ${import_chalk.default.bold("Summary:")}`);
|
|
1606
|
+
if (acceptedFixes.length > 0) {
|
|
1607
|
+
console.log(import_chalk.default.green(` \u2714 ${acceptedFixes.length} fixes applied`));
|
|
1483
1608
|
}
|
|
1484
|
-
if (
|
|
1485
|
-
console.log(import_chalk.default.
|
|
1609
|
+
if (skippedFixes.length > 0) {
|
|
1610
|
+
console.log(import_chalk.default.yellow(` \u2298 ${skippedFixes.length} fixes skipped (saved to .cencori-fixes.json)`));
|
|
1611
|
+
}
|
|
1612
|
+
if (acceptedFixes.length > 0) {
|
|
1613
|
+
console.log();
|
|
1614
|
+
console.log(import_chalk.default.cyan(` Run ${import_chalk.default.bold("npx @cencori/scan")} again to verify your fixes!`));
|
|
1486
1615
|
}
|
|
1487
1616
|
console.log();
|
|
1488
1617
|
} catch (error) {
|