eslint-plugin-ai-guard 1.2.3 → 1.2.4
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/index.js +211 -28
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1259,11 +1259,13 @@ function sanitizeSarifTags(tags) {
|
|
|
1259
1259
|
);
|
|
1260
1260
|
}
|
|
1261
1261
|
function sanitizeSarifProperties(props) {
|
|
1262
|
+
const confidence = props.confidence ?? "low";
|
|
1262
1263
|
return {
|
|
1263
1264
|
tags: sanitizeSarifTags(props.tags),
|
|
1264
|
-
confidence
|
|
1265
|
+
confidence,
|
|
1265
1266
|
category: props.category ?? "AI Patterns",
|
|
1266
|
-
"
|
|
1267
|
+
"security-severity": confidenceToSecuritySeverity(confidence),
|
|
1268
|
+
precision: confidenceToPrecision(confidence)
|
|
1267
1269
|
};
|
|
1268
1270
|
}
|
|
1269
1271
|
function sanitizeSarifRule(rule) {
|
|
@@ -1295,6 +1297,34 @@ function sanitizeSarifLog(log2) {
|
|
|
1295
1297
|
}))
|
|
1296
1298
|
};
|
|
1297
1299
|
}
|
|
1300
|
+
function confidenceToSecuritySeverity(confidence) {
|
|
1301
|
+
switch (confidence) {
|
|
1302
|
+
case "high":
|
|
1303
|
+
return "8.0";
|
|
1304
|
+
case "medium":
|
|
1305
|
+
return "5.0";
|
|
1306
|
+
case "low":
|
|
1307
|
+
return "3.0";
|
|
1308
|
+
case "informational":
|
|
1309
|
+
return "1.0";
|
|
1310
|
+
default:
|
|
1311
|
+
return "3.0";
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
function confidenceToPrecision(confidence) {
|
|
1315
|
+
switch (confidence) {
|
|
1316
|
+
case "high":
|
|
1317
|
+
return "high";
|
|
1318
|
+
case "medium":
|
|
1319
|
+
return "medium";
|
|
1320
|
+
case "low":
|
|
1321
|
+
return "low";
|
|
1322
|
+
case "informational":
|
|
1323
|
+
return "low";
|
|
1324
|
+
default:
|
|
1325
|
+
return "low";
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1298
1328
|
function confidenceToSarifLevel(confidence) {
|
|
1299
1329
|
switch (confidence) {
|
|
1300
1330
|
case "high":
|
|
@@ -1323,20 +1353,6 @@ function confidenceToRank(confidence) {
|
|
|
1323
1353
|
return 30;
|
|
1324
1354
|
}
|
|
1325
1355
|
}
|
|
1326
|
-
function confidenceToGitHubSeverity(confidence) {
|
|
1327
|
-
switch (confidence) {
|
|
1328
|
-
case "high":
|
|
1329
|
-
return "high";
|
|
1330
|
-
case "medium":
|
|
1331
|
-
return "medium";
|
|
1332
|
-
case "low":
|
|
1333
|
-
return "low";
|
|
1334
|
-
case "informational":
|
|
1335
|
-
return "recommendation";
|
|
1336
|
-
default:
|
|
1337
|
-
return "low";
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
1356
|
function filePathToUri(filePath) {
|
|
1341
1357
|
return filePath.replace(/\\/g, "/");
|
|
1342
1358
|
}
|
|
@@ -1384,8 +1400,7 @@ function buildRuleDescriptors(ruleIds) {
|
|
|
1384
1400
|
properties: sanitizeSarifProperties({
|
|
1385
1401
|
tags: rawTags,
|
|
1386
1402
|
confidence,
|
|
1387
|
-
category
|
|
1388
|
-
problemSeverity: confidenceToGitHubSeverity(confidence)
|
|
1403
|
+
category
|
|
1389
1404
|
})
|
|
1390
1405
|
});
|
|
1391
1406
|
});
|
|
@@ -1419,11 +1434,87 @@ function buildSarifResult(issue, file) {
|
|
|
1419
1434
|
],
|
|
1420
1435
|
properties: {
|
|
1421
1436
|
confidence: confidence ?? "low",
|
|
1437
|
+
"security-severity": confidenceToSecuritySeverity(confidence),
|
|
1438
|
+
precision: confidenceToPrecision(confidence),
|
|
1422
1439
|
...asyncRiskType && { asyncRiskType },
|
|
1423
1440
|
...category && { category }
|
|
1424
1441
|
}
|
|
1425
1442
|
};
|
|
1426
1443
|
}
|
|
1444
|
+
function enhanceTagsWithGitHubSemantics(ruleId, baseTags) {
|
|
1445
|
+
const category = ISSUE_CATEGORY[ruleId];
|
|
1446
|
+
const tags = [...baseTags];
|
|
1447
|
+
if (category === "Security" || ruleId.includes("secret") || ruleId.includes("eval") || ruleId.includes("sql") || ruleId.includes("deserialize") || ruleId.includes("auth")) {
|
|
1448
|
+
tags.push("security", "correctness", "reliability");
|
|
1449
|
+
} else {
|
|
1450
|
+
tags.push("correctness", "reliability");
|
|
1451
|
+
}
|
|
1452
|
+
return sanitizeSarifTags(tags);
|
|
1453
|
+
}
|
|
1454
|
+
function normalizeSarifForGitHub(log2) {
|
|
1455
|
+
const normalizedRuns = log2.runs.map((run) => {
|
|
1456
|
+
const rules = run.tool.driver.rules.map((rule) => {
|
|
1457
|
+
const confidence = ISSUE_CONFIDENCE[rule.id];
|
|
1458
|
+
const properties = rule.properties || {};
|
|
1459
|
+
properties["security-severity"] = confidenceToSecuritySeverity(confidence);
|
|
1460
|
+
properties["precision"] = confidenceToPrecision(confidence);
|
|
1461
|
+
const currentTags = properties.tags || [];
|
|
1462
|
+
properties.tags = enhanceTagsWithGitHubSemantics(rule.id, currentTags);
|
|
1463
|
+
delete properties["problem.severity"];
|
|
1464
|
+
const defaultConfiguration = rule.defaultConfiguration || { level: "warning" };
|
|
1465
|
+
let normalizedConfigLevel = defaultConfiguration.level;
|
|
1466
|
+
if (!["error", "warning", "note", "none"].includes(normalizedConfigLevel)) {
|
|
1467
|
+
normalizedConfigLevel = confidenceToSarifLevel(confidence);
|
|
1468
|
+
}
|
|
1469
|
+
if (normalizedConfigLevel === "none") {
|
|
1470
|
+
normalizedConfigLevel = "note";
|
|
1471
|
+
}
|
|
1472
|
+
return {
|
|
1473
|
+
...rule,
|
|
1474
|
+
defaultConfiguration: {
|
|
1475
|
+
...defaultConfiguration,
|
|
1476
|
+
level: normalizedConfigLevel
|
|
1477
|
+
},
|
|
1478
|
+
properties
|
|
1479
|
+
};
|
|
1480
|
+
});
|
|
1481
|
+
const results = run.results.map((result) => {
|
|
1482
|
+
const confidence = ISSUE_CONFIDENCE[result.ruleId];
|
|
1483
|
+
const properties = result.properties || {};
|
|
1484
|
+
properties["security-severity"] = confidenceToSecuritySeverity(confidence);
|
|
1485
|
+
properties["precision"] = confidenceToPrecision(confidence);
|
|
1486
|
+
let normalizedLevel = result.level;
|
|
1487
|
+
if (!["error", "warning", "note", "none"].includes(normalizedLevel)) {
|
|
1488
|
+
normalizedLevel = confidenceToSarifLevel(confidence);
|
|
1489
|
+
}
|
|
1490
|
+
if (normalizedLevel === "none") {
|
|
1491
|
+
normalizedLevel = "note";
|
|
1492
|
+
}
|
|
1493
|
+
const kind = confidence === "informational" ? "informational" : "fail";
|
|
1494
|
+
return {
|
|
1495
|
+
...result,
|
|
1496
|
+
level: normalizedLevel,
|
|
1497
|
+
kind,
|
|
1498
|
+
properties
|
|
1499
|
+
};
|
|
1500
|
+
});
|
|
1501
|
+
return {
|
|
1502
|
+
...run,
|
|
1503
|
+
tool: {
|
|
1504
|
+
...run.tool,
|
|
1505
|
+
driver: {
|
|
1506
|
+
...run.tool.driver,
|
|
1507
|
+
rules
|
|
1508
|
+
}
|
|
1509
|
+
},
|
|
1510
|
+
results
|
|
1511
|
+
};
|
|
1512
|
+
});
|
|
1513
|
+
return {
|
|
1514
|
+
...log2,
|
|
1515
|
+
runs: normalizedRuns
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1427
1518
|
function buildSarifLog(result, version = PKG_VERSION) {
|
|
1428
1519
|
const usedRuleIds = collectUsedRuleIds(result);
|
|
1429
1520
|
const rules = buildRuleDescriptors(usedRuleIds);
|
|
@@ -1457,7 +1548,7 @@ function buildSarifLog(result, version = PKG_VERSION) {
|
|
|
1457
1548
|
}
|
|
1458
1549
|
]
|
|
1459
1550
|
};
|
|
1460
|
-
return sanitizeSarifLog(log2);
|
|
1551
|
+
return normalizeSarifForGitHub(sanitizeSarifLog(log2));
|
|
1461
1552
|
}
|
|
1462
1553
|
function sarifToJson(log2) {
|
|
1463
1554
|
return JSON.stringify(log2, null, 2);
|
|
@@ -1470,7 +1561,7 @@ function buildSarifDebugInfo(result) {
|
|
|
1470
1561
|
const category = ISSUE_CATEGORY[id] ?? "AI Patterns";
|
|
1471
1562
|
const categorySlug = category.toLowerCase().replace(/\s+/g, "-");
|
|
1472
1563
|
const rawTags = [...meta?.tags ?? ["ai-guard"], categorySlug];
|
|
1473
|
-
const sanitizedTags =
|
|
1564
|
+
const sanitizedTags = enhanceTagsWithGitHubSemantics(id, rawTags);
|
|
1474
1565
|
return {
|
|
1475
1566
|
id,
|
|
1476
1567
|
rawTags,
|
|
@@ -1478,11 +1569,29 @@ function buildSarifDebugInfo(result) {
|
|
|
1478
1569
|
hasDuplicatesInRaw: rawTags.length !== new Set(rawTags).size,
|
|
1479
1570
|
confidence: confidence ?? "low",
|
|
1480
1571
|
category,
|
|
1481
|
-
level: confidenceToSarifLevel(confidence)
|
|
1572
|
+
level: confidenceToSarifLevel(confidence),
|
|
1573
|
+
securitySeverity: confidenceToSecuritySeverity(confidence),
|
|
1574
|
+
precision: confidenceToPrecision(confidence)
|
|
1482
1575
|
};
|
|
1483
1576
|
});
|
|
1577
|
+
const resultsEmitted = [];
|
|
1578
|
+
for (const file of result.files) {
|
|
1579
|
+
for (const issue of file.issues) {
|
|
1580
|
+
const confidence = ISSUE_CONFIDENCE[issue.ruleId];
|
|
1581
|
+
resultsEmitted.push({
|
|
1582
|
+
ruleId: issue.ruleId,
|
|
1583
|
+
level: confidenceToSarifLevel(confidence),
|
|
1584
|
+
kind: confidence === "informational" ? "informational" : "fail",
|
|
1585
|
+
securitySeverity: confidenceToSecuritySeverity(confidence),
|
|
1586
|
+
precision: confidenceToPrecision(confidence),
|
|
1587
|
+
filePath: file.filePath,
|
|
1588
|
+
line: issue.line
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1484
1592
|
return {
|
|
1485
1593
|
rulesEmitted,
|
|
1594
|
+
resultsEmitted,
|
|
1486
1595
|
totalResults: result.files.reduce((n, f) => n + f.issues.length, 0),
|
|
1487
1596
|
version: PKG_VERSION
|
|
1488
1597
|
};
|
|
@@ -2021,21 +2130,47 @@ function registerRunCommand(program2) {
|
|
|
2021
2130
|
const sarifJson = sarifToJson(sarifLog);
|
|
2022
2131
|
if (opts.debugSarif) {
|
|
2023
2132
|
const debugInfo = buildSarifDebugInfo(result);
|
|
2024
|
-
process.stderr.write("\n[debug-sarif] SARIF
|
|
2133
|
+
process.stderr.write("\n[debug-sarif] SARIF GitHub Compatibility Trace\n");
|
|
2025
2134
|
process.stderr.write("[debug-sarif] \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\n");
|
|
2135
|
+
process.stderr.write("[debug-sarif] Rules Emitted:\n");
|
|
2026
2136
|
for (const r of debugInfo.rulesEmitted) {
|
|
2027
|
-
process.stderr.write(`[debug-sarif] ${r.id}
|
|
2137
|
+
process.stderr.write(`[debug-sarif] - ${r.id}:
|
|
2138
|
+
`);
|
|
2139
|
+
process.stderr.write(`[debug-sarif] confidence: ${r.confidence}
|
|
2140
|
+
`);
|
|
2141
|
+
process.stderr.write(`[debug-sarif] level: ${r.level}
|
|
2142
|
+
`);
|
|
2143
|
+
process.stderr.write(`[debug-sarif] security-severity: ${r.securitySeverity}
|
|
2144
|
+
`);
|
|
2145
|
+
process.stderr.write(`[debug-sarif] precision: ${r.precision}
|
|
2028
2146
|
`);
|
|
2029
|
-
process.stderr.write(`[debug-sarif]
|
|
2147
|
+
process.stderr.write(`[debug-sarif] raw tags: ${JSON.stringify(r.rawTags)}
|
|
2030
2148
|
`);
|
|
2031
|
-
process.stderr.write(`[debug-sarif]
|
|
2149
|
+
process.stderr.write(`[debug-sarif] normalized tags: ${JSON.stringify(r.sanitizedTags)}
|
|
2032
2150
|
`);
|
|
2033
2151
|
if (r.hasDuplicatesInRaw) {
|
|
2034
|
-
process.stderr.write(`[debug-sarif]
|
|
2152
|
+
process.stderr.write(`[debug-sarif] \u26A0 had duplicates \u2014 deduplicated
|
|
2153
|
+
`);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
process.stderr.write("\n[debug-sarif] Results Emitted:\n");
|
|
2157
|
+
if (debugInfo.resultsEmitted.length === 0) {
|
|
2158
|
+
process.stderr.write("[debug-sarif] (none)\n");
|
|
2159
|
+
} else {
|
|
2160
|
+
for (const res of debugInfo.resultsEmitted) {
|
|
2161
|
+
process.stderr.write(`[debug-sarif] - ${res.ruleId} (${res.filePath}:${res.line}):
|
|
2162
|
+
`);
|
|
2163
|
+
process.stderr.write(`[debug-sarif] level: ${res.level}
|
|
2164
|
+
`);
|
|
2165
|
+
process.stderr.write(`[debug-sarif] kind: ${res.kind}
|
|
2166
|
+
`);
|
|
2167
|
+
process.stderr.write(`[debug-sarif] security-severity: ${res.securitySeverity}
|
|
2168
|
+
`);
|
|
2169
|
+
process.stderr.write(`[debug-sarif] precision: ${res.precision}
|
|
2035
2170
|
`);
|
|
2036
2171
|
}
|
|
2037
2172
|
}
|
|
2038
|
-
process.stderr.write("\n");
|
|
2173
|
+
process.stderr.write("[debug-sarif] \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\n\n");
|
|
2039
2174
|
}
|
|
2040
2175
|
if (opts.sarifOutput) {
|
|
2041
2176
|
import_fs4.default.writeFileSync(opts.sarifOutput, sarifJson, "utf-8");
|
|
@@ -2388,7 +2523,7 @@ function registerChangedCommand(program2) {
|
|
|
2388
2523
|
"--max-warnings <n>",
|
|
2389
2524
|
"Fail if warnings exceed this count",
|
|
2390
2525
|
(v) => Number.parseInt(v, 10)
|
|
2391
|
-
).option("--verbose", "Expand all findings (disable grouping)").option("--quiet", "Show errors only").option("--debug-timing", "Print per-phase timing diagnostics").option("--debug-git", "Print full git resolution trace (useful for diagnosing zero-file issues)").option("--debug-ci", "Print CI environment state (GITHUB_* vars, SARIF path, summary path)").action(async (opts) => {
|
|
2526
|
+
).option("--verbose", "Expand all findings (disable grouping)").option("--quiet", "Show errors only").option("--debug-timing", "Print per-phase timing diagnostics").option("--debug-git", "Print full git resolution trace (useful for diagnosing zero-file issues)").option("--debug-ci", "Print CI environment state (GITHUB_* vars, SARIF path, summary path)").option("--debug-sarif", "Print SARIF tag normalization trace \u2014 diagnose duplicate-tag schema issues").action(async (opts) => {
|
|
2392
2527
|
const preset = opts.strict ? "strict" : opts.security ? "security" : "recommended";
|
|
2393
2528
|
const inCI = isGitHubActions();
|
|
2394
2529
|
const baseRef = opts.base ?? (opts.pr || inCI ? getGitHubBaseRef() : void 0);
|
|
@@ -2500,8 +2635,56 @@ function handleResults(result, opts, preset, scanMode, changedFilesCount, inCI,
|
|
|
2500
2635
|
if (opts.sarif) {
|
|
2501
2636
|
const sarifLog = buildSarifLog(result);
|
|
2502
2637
|
const sarifJson = sarifToJson(sarifLog);
|
|
2638
|
+
if (opts.debugSarif) {
|
|
2639
|
+
const debugInfo = buildSarifDebugInfo(result);
|
|
2640
|
+
process.stderr.write("\n[debug-sarif] SARIF GitHub Compatibility Trace\n");
|
|
2641
|
+
process.stderr.write("[debug-sarif] \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\n");
|
|
2642
|
+
process.stderr.write("[debug-sarif] Rules Emitted:\n");
|
|
2643
|
+
for (const r of debugInfo.rulesEmitted) {
|
|
2644
|
+
process.stderr.write(`[debug-sarif] - ${r.id}:
|
|
2645
|
+
`);
|
|
2646
|
+
process.stderr.write(`[debug-sarif] confidence: ${r.confidence}
|
|
2647
|
+
`);
|
|
2648
|
+
process.stderr.write(`[debug-sarif] level: ${r.level}
|
|
2649
|
+
`);
|
|
2650
|
+
process.stderr.write(`[debug-sarif] security-severity: ${r.securitySeverity}
|
|
2651
|
+
`);
|
|
2652
|
+
process.stderr.write(`[debug-sarif] precision: ${r.precision}
|
|
2653
|
+
`);
|
|
2654
|
+
process.stderr.write(`[debug-sarif] raw tags: ${JSON.stringify(r.rawTags)}
|
|
2655
|
+
`);
|
|
2656
|
+
process.stderr.write(`[debug-sarif] normalized tags: ${JSON.stringify(r.sanitizedTags)}
|
|
2657
|
+
`);
|
|
2658
|
+
if (r.hasDuplicatesInRaw) {
|
|
2659
|
+
process.stderr.write(`[debug-sarif] \u26A0 had duplicates \u2014 deduplicated
|
|
2660
|
+
`);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
process.stderr.write("\n[debug-sarif] Results Emitted:\n");
|
|
2664
|
+
if (debugInfo.resultsEmitted.length === 0) {
|
|
2665
|
+
process.stderr.write("[debug-sarif] (none)\n");
|
|
2666
|
+
} else {
|
|
2667
|
+
for (const res of debugInfo.resultsEmitted) {
|
|
2668
|
+
process.stderr.write(`[debug-sarif] - ${res.ruleId} (${res.filePath}:${res.line}):
|
|
2669
|
+
`);
|
|
2670
|
+
process.stderr.write(`[debug-sarif] level: ${res.level}
|
|
2671
|
+
`);
|
|
2672
|
+
process.stderr.write(`[debug-sarif] kind: ${res.kind}
|
|
2673
|
+
`);
|
|
2674
|
+
process.stderr.write(`[debug-sarif] security-severity: ${res.securitySeverity}
|
|
2675
|
+
`);
|
|
2676
|
+
process.stderr.write(`[debug-sarif] precision: ${res.precision}
|
|
2677
|
+
`);
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
process.stderr.write("[debug-sarif] \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\n\n");
|
|
2681
|
+
}
|
|
2503
2682
|
if (resolvedSarifPath) {
|
|
2504
2683
|
import_fs5.default.writeFileSync(resolvedSarifPath, sarifJson, "utf-8");
|
|
2684
|
+
if (opts.debugSarif) {
|
|
2685
|
+
process.stderr.write(`[debug-sarif] SARIF written to: ${resolvedSarifPath}
|
|
2686
|
+
`);
|
|
2687
|
+
}
|
|
2505
2688
|
} else {
|
|
2506
2689
|
console.log(sarifJson);
|
|
2507
2690
|
}
|