@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 CHANGED
@@ -856,17 +856,29 @@ async function generateFixes(issues, fileContents) {
856
856
  "Authorization": `Bearer ${apiKey}`
857
857
  },
858
858
  body: JSON.stringify({
859
- model: "meta-llama/llama-4-scout-17b-16e-instruct",
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. For secrets, use environment variables. For XSS, use sanitization. Respond in JSON: {"fixedCode": "the fixed code snippet", "explanation": "what was changed"}`
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
- Generate a secure fix.`
889
+ Respond with JSON only.`
878
890
  }
879
891
  ],
880
892
  temperature: 0,
881
- max_tokens: 500
893
+ max_tokens: 1e3
882
894
  })
883
895
  });
884
896
  if (!response.ok) {
885
- throw new Error(`API error: ${response.status}`);
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
- const content_response = data.choices[0]?.message?.content || "{}";
889
- const parsed = JSON.parse(content_response);
890
- results.push({
891
- issue,
892
- originalCode: codeSnippet,
893
- fixedCode: parsed.fixedCode || codeSnippet,
894
- explanation: parsed.explanation || "No explanation provided",
895
- applied: false
896
- });
897
- } catch {
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: "Unable to generate fix - manual review required",
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.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 applied = appliedFixes.filter((f) => f.applied);
1423
- const notApplied = appliedFixes.filter((f) => !f.applied);
1424
- if (applied.length > 0) {
1425
- console.log(` ${import_chalk.default.bold.green("\u2714 Applied fixes:")}`);
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 (notApplied.length > 0) {
1433
- console.log(` ${import_chalk.default.bold.yellow("\u26A0 Suggested fixes (require manual review):")}`);
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
- for (let i = 0; i < Math.min(notApplied.length, 5); i++) {
1436
- const fix = notApplied[i];
1437
- console.log(import_chalk.default.cyan(` \u2500\u2500\u2500 ${fix.issue.file}:${fix.issue.line} \u2500\u2500\u2500`));
1438
- console.log(import_chalk.default.gray(` Issue: ${fix.issue.name}`));
1439
- console.log();
1440
- const origLines = fix.originalCode.split("\n").slice(0, 5);
1441
- console.log(import_chalk.default.red(" - Original:"));
1442
- origLines.forEach((line) => console.log(import_chalk.default.red(` ${line}`)));
1443
- if (fix.originalCode.split("\n").length > 5) {
1444
- console.log(import_chalk.default.gray(" ... (truncated)"));
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
- const fixLines = fix.fixedCode.split("\n").slice(0, 5);
1448
- console.log(import_chalk.default.green(" + Suggested fix:"));
1449
- fixLines.forEach((line) => console.log(import_chalk.default.green(` ${line}`)));
1450
- if (fix.fixedCode.split("\n").length > 5) {
1451
- console.log(import_chalk.default.gray(" ... (truncated)"));
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.gray(` Explanation: ${fix.explanation}`));
1454
- console.log();
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
- if (notApplied.length > 5) {
1457
- console.log(import_chalk.default.gray(` ... and ${notApplied.length - 5} more fixes`));
1458
- console.log();
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: notApplied.length,
1464
- fixes: notApplied.map((f) => ({
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 (applied.length === 0 && notApplied.length === 0) {
1485
- console.log(import_chalk.default.green(" No fixes needed!"));
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) {