eslint-plugin-ai-guard 1.2.6 → 1.2.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/dist/cli/index.js CHANGED
@@ -1206,6 +1206,7 @@ async function runEslint(options) {
1206
1206
  }
1207
1207
 
1208
1208
  // cli/utils/sarif.ts
1209
+ var import_crypto = require("crypto");
1209
1210
  var import_path3 = __toESM(require("path"));
1210
1211
 
1211
1212
  // cli/utils/version.ts
@@ -1233,6 +1234,8 @@ function readVersion() {
1233
1234
  var PKG_VERSION = _version ?? (_version = readVersion());
1234
1235
 
1235
1236
  // cli/utils/sarif.ts
1237
+ var SARIF_AUTOMATION_ID = "ai-guard";
1238
+ var FINGERPRINT_KEY = "ai-guard/v1";
1236
1239
  var RULE_DOCS = {
1237
1240
  "ai-guard/no-empty-catch": { shortDesc: "Empty catch block silently swallows errors", tags: ["reliability"] },
1238
1241
  "ai-guard/no-broad-exception": { shortDesc: "Catching broad Exception/Error masks specific failures", tags: ["reliability"] },
@@ -1375,6 +1378,16 @@ function normalizeSarifPath(filePath, repoRoot) {
1375
1378
  }
1376
1379
  return normalized;
1377
1380
  }
1381
+ function generateStableFingerprint(normalizedUri, ruleId, startLine, message) {
1382
+ const normalizedMessage = message.trim().toLowerCase().replace(/\s+/g, " ");
1383
+ const input = [
1384
+ ruleId,
1385
+ normalizedUri.toLowerCase(),
1386
+ String(startLine),
1387
+ normalizedMessage
1388
+ ].join(":");
1389
+ return (0, import_crypto.createHash)("sha256").update(input, "utf8").digest("hex");
1390
+ }
1378
1391
  function collectUsedRuleIds(result) {
1379
1392
  const ids = /* @__PURE__ */ new Set();
1380
1393
  for (const file of result.files) {
@@ -1429,12 +1442,23 @@ function buildSarifResult(issue, file, repoRoot) {
1429
1442
  const asyncRiskType = ISSUE_ASYNC_RISK_TYPE[issue.ruleId];
1430
1443
  const category = ISSUE_CATEGORY[issue.ruleId];
1431
1444
  const sarifUri = normalizeSarifPath(file.filePath, repoRoot);
1445
+ const fingerprint = generateStableFingerprint(
1446
+ sarifUri,
1447
+ issue.ruleId,
1448
+ Math.max(1, issue.line),
1449
+ issue.message
1450
+ );
1432
1451
  return {
1433
1452
  ruleId: issue.ruleId,
1434
1453
  level: confidenceToSarifLevel(confidence),
1435
1454
  kind: confidence === "informational" ? "informational" : "fail",
1436
1455
  rank: confidenceToRank(confidence),
1437
1456
  message: { text: issue.message },
1457
+ // partialFingerprints: per-result hash for GitHub alert deduplication.
1458
+ // Without this, GitHub closes and reopens every alert on every scan run.
1459
+ partialFingerprints: {
1460
+ [FINGERPRINT_KEY]: fingerprint
1461
+ },
1438
1462
  locations: [
1439
1463
  {
1440
1464
  physicalLocation: {
@@ -1557,6 +1581,12 @@ function buildSarifLog(result, version = PKG_VERSION, repoRoot) {
1557
1581
  rules
1558
1582
  }
1559
1583
  },
1584
+ // automationDetails.id: stable tool identifier for GitHub Code Scanning.
1585
+ // Groups all ai-guard scan runs into one persistent analysis slot.
1586
+ // MUST remain "ai-guard" forever — changing this orphans all existing alerts.
1587
+ automationDetails: {
1588
+ id: SARIF_AUTOMATION_ID
1589
+ },
1560
1590
  results: sarifResults,
1561
1591
  properties: {
1562
1592
  preset: "recommended",
@@ -1616,6 +1646,13 @@ function buildSarifDebugInfo(result, repoRoot) {
1616
1646
  }
1617
1647
  for (const issue of file.issues) {
1618
1648
  const confidence = ISSUE_CONFIDENCE[issue.ruleId];
1649
+ const normalizedUri = normalizeSarifPath(file.filePath, repoRoot);
1650
+ const fingerprint = generateStableFingerprint(
1651
+ normalizedUri,
1652
+ issue.ruleId,
1653
+ Math.max(1, issue.line),
1654
+ issue.message
1655
+ );
1619
1656
  resultsEmitted.push({
1620
1657
  ruleId: issue.ruleId,
1621
1658
  level: confidenceToSarifLevel(confidence),
@@ -1623,12 +1660,15 @@ function buildSarifDebugInfo(result, repoRoot) {
1623
1660
  securitySeverity: confidenceToSecuritySeverity(confidence),
1624
1661
  precision: confidenceToPrecision(confidence),
1625
1662
  filePath: file.filePath,
1626
- normalizedUri: normalizeSarifPath(file.filePath, repoRoot),
1627
- line: issue.line
1663
+ normalizedUri,
1664
+ line: issue.line,
1665
+ fingerprint
1628
1666
  });
1629
1667
  }
1630
1668
  }
1631
1669
  return {
1670
+ automationId: SARIF_AUTOMATION_ID,
1671
+ fingerprintKey: FINGERPRINT_KEY,
1632
1672
  rulesEmitted,
1633
1673
  resultsEmitted,
1634
1674
  pathsDebug,
@@ -2113,7 +2153,7 @@ function registerRunCommand(program2) {
2113
2153
  "--max-warnings <n>",
2114
2154
  "Fail with exit code 1 if warnings exceed this count",
2115
2155
  (value) => Number.parseInt(value, 10)
2116
- ).option("--verbose", "Show all issues \u2014 disable grouping of repeated warnings").option("--quiet", "Only show errors \u2014 suppress warnings and informational hints").option("--debug-timing", "Print per-phase timing diagnostics").option("--debug-sarif", "Print SARIF tag/level normalization trace \u2014 diagnose schema issues").option("--debug-sarif-paths", "Print path normalization trace \u2014 diagnose GitHub Code Scanning path resolution").option("--sarif-output <file>", "Write SARIF to this file instead of stdout").action(async (opts) => {
2156
+ ).option("--verbose", "Show all issues \u2014 disable grouping of repeated warnings").option("--quiet", "Only show errors \u2014 suppress warnings and informational hints").option("--debug-timing", "Print per-phase timing diagnostics").option("--debug-sarif", "Print SARIF tag/level normalization trace \u2014 diagnose schema issues").option("--debug-sarif-paths", "Print path normalization trace \u2014 diagnose GitHub Code Scanning path resolution").option("--debug-sarif-persistence", "Print persistence identity trace \u2014 fingerprints, automationDetails, category").option("--sarif-output <file>", "Write SARIF to this file instead of stdout").action(async (opts) => {
2117
2157
  if (opts.maxWarnings !== void 0 && (!Number.isInteger(opts.maxWarnings) || opts.maxWarnings < 0)) {
2118
2158
  log.blank();
2119
2159
  log.error("--max-warnings must be a non-negative integer.");
@@ -2241,6 +2281,34 @@ function registerRunCommand(program2) {
2241
2281
  }
2242
2282
  process.stderr.write("[debug-sarif-paths] \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");
2243
2283
  }
2284
+ if (opts.debugSarifPersistence) {
2285
+ const debugInfo = buildSarifDebugInfo(result, repoRoot);
2286
+ process.stderr.write("\n[debug-sarif-persistence] GitHub Persistence Identity Trace\n");
2287
+ process.stderr.write("[debug-sarif-persistence] \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");
2288
+ process.stderr.write(`[debug-sarif-persistence] automationDetails.id: ${debugInfo.automationId}
2289
+ `);
2290
+ process.stderr.write(`[debug-sarif-persistence] fingerprintKey: ${debugInfo.fingerprintKey}
2291
+ `);
2292
+ process.stderr.write(`[debug-sarif-persistence] GITHUB_WORKSPACE: ${repoRoot ?? "(not set)"}
2293
+ `);
2294
+ process.stderr.write(`[debug-sarif-persistence] totalResults: ${debugInfo.totalResults}
2295
+
2296
+ `);
2297
+ if (debugInfo.resultsEmitted.length === 0) {
2298
+ process.stderr.write("[debug-sarif-persistence] (no findings \u2014 no fingerprints to show)\n");
2299
+ } else {
2300
+ process.stderr.write("[debug-sarif-persistence] Fingerprints (stable identity per finding):\n");
2301
+ for (const res of debugInfo.resultsEmitted) {
2302
+ process.stderr.write(`[debug-sarif-persistence] \u2500 ${res.ruleId}
2303
+ `);
2304
+ process.stderr.write(`[debug-sarif-persistence] file: ${res.normalizedUri}:${res.line}
2305
+ `);
2306
+ process.stderr.write(`[debug-sarif-persistence] fingerprint: ${res.fingerprint}
2307
+ `);
2308
+ }
2309
+ }
2310
+ process.stderr.write("[debug-sarif-persistence] \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");
2311
+ }
2244
2312
  if (opts.sarifOutput) {
2245
2313
  import_fs4.default.writeFileSync(opts.sarifOutput, sarifJson, "utf-8");
2246
2314
  if (opts.debugSarif) {
@@ -2592,7 +2660,7 @@ function registerChangedCommand(program2) {
2592
2660
  "--max-warnings <n>",
2593
2661
  "Fail if warnings exceed this count",
2594
2662
  (v) => Number.parseInt(v, 10)
2595
- ).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/level normalization trace \u2014 diagnose schema issues").option("--debug-sarif-paths", "Print path normalization trace \u2014 diagnose GitHub Code Scanning path resolution").action(async (opts) => {
2663
+ ).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/level normalization trace \u2014 diagnose schema issues").option("--debug-sarif-paths", "Print path normalization trace \u2014 diagnose GitHub Code Scanning path resolution").option("--debug-sarif-persistence", "Print persistence identity trace \u2014 fingerprints, automationDetails, category").action(async (opts) => {
2596
2664
  const preset = opts.strict ? "strict" : opts.security ? "security" : "recommended";
2597
2665
  const inCI = isGitHubActions();
2598
2666
  const baseRef = opts.base ?? (opts.pr || inCI ? getGitHubBaseRef() : void 0);
@@ -2777,6 +2845,34 @@ function handleResults(result, opts, preset, scanMode, changedFilesCount, inCI,
2777
2845
  }
2778
2846
  process.stderr.write("[debug-sarif-paths] \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");
2779
2847
  }
2848
+ if (opts.debugSarifPersistence) {
2849
+ const debugInfo = buildSarifDebugInfo(result, repoRoot);
2850
+ process.stderr.write("\n[debug-sarif-persistence] GitHub Persistence Identity Trace\n");
2851
+ process.stderr.write("[debug-sarif-persistence] \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");
2852
+ process.stderr.write(`[debug-sarif-persistence] automationDetails.id: ${debugInfo.automationId}
2853
+ `);
2854
+ process.stderr.write(`[debug-sarif-persistence] fingerprintKey: ${debugInfo.fingerprintKey}
2855
+ `);
2856
+ process.stderr.write(`[debug-sarif-persistence] GITHUB_WORKSPACE: ${repoRoot ?? "(not set)"}
2857
+ `);
2858
+ process.stderr.write(`[debug-sarif-persistence] totalResults: ${debugInfo.totalResults}
2859
+
2860
+ `);
2861
+ if (debugInfo.resultsEmitted.length === 0) {
2862
+ process.stderr.write("[debug-sarif-persistence] (no findings \u2014 no fingerprints to show)\n");
2863
+ } else {
2864
+ process.stderr.write("[debug-sarif-persistence] Fingerprints (stable identity per finding):\n");
2865
+ for (const res of debugInfo.resultsEmitted) {
2866
+ process.stderr.write(`[debug-sarif-persistence] \u2500 ${res.ruleId}
2867
+ `);
2868
+ process.stderr.write(`[debug-sarif-persistence] file: ${res.normalizedUri}:${res.line}
2869
+ `);
2870
+ process.stderr.write(`[debug-sarif-persistence] fingerprint: ${res.fingerprint}
2871
+ `);
2872
+ }
2873
+ }
2874
+ process.stderr.write("[debug-sarif-persistence] \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");
2875
+ }
2780
2876
  if (resolvedSarifPath) {
2781
2877
  import_fs5.default.writeFileSync(resolvedSarifPath, sarifJson, "utf-8");
2782
2878
  if (opts.debugSarif) {