cognium-dev 4.13.2 → 4.14.0

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.
Files changed (3) hide show
  1. package/README.md +4 -10
  2. package/dist/cli.js +208 -35
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -307,9 +307,9 @@ Filter to security findings only: `cognium-dev scan ./src --category security`
307
307
  | Rust | `.rs` | Actix-web, Rocket, Axum |
308
308
  | Bash | `.sh`, `.bash` | Shell scripts |
309
309
  | HTML | `.html`, `.htm` | Web extraction preprocessor |
310
- | C#/.NET _(experimental)_ | `.cs` | ASP.NET Core, ADO.NET, EF Core |
310
+ | C#/.NET _(Preview)_ | `.cs` | ASP.NET Core, ADO.NET (EF Core not yet verified) |
311
311
 
312
- > **C#/.NET is experimental / preview** (since 4.0.0). Straight-line taint
312
+ > **C#/.NET is Preview** (since 4.0.0). Straight-line taint
313
313
  > analysis across 10 CWE families (SQLi, command injection, path traversal,
314
314
  > SSRF, code injection, XSS, deserialization, LDAP, XPath, XXE). Not yet
315
315
  > benchmark-verified — expect gaps in branch/alias precision and detector breadth.
@@ -455,15 +455,9 @@ Cognium is built for speed:
455
455
  - **Core Engine**: [circle-ir](https://github.com/cogniumhq/cognium-dev/tree/main/packages/circle-ir) - High-performance SAST library
456
456
  - **Dependencies**: Only 1 runtime dependency (circle-ir)
457
457
 
458
- ## LLM Enhancement (Optional)
458
+ ## Data handling
459
459
 
460
- The core static analysis engine runs deterministically without any LLM. Optionally, you can enable LLM-based discovery modes for enhanced detection:
461
-
462
- - **Discovery Mode**: LLM reads source code to locate vulnerable methods from scratch
463
- - **Verification Mode**: Confirms whether static findings are actually exploitable
464
- - **Semantic Extraction**: Extracts design intent for automated gap analysis
465
-
466
- For details on LLM integration and benchmark improvements (42.5% → 78.3% on CWE-Bench with Claude Opus), visit [cognium.net](https://cognium.net).
460
+ cognium-dev is fully deterministic and runs entirely on your machine: source code is not sent to Cognium or any third-party service. Optional AI-assisted analysis is a separate product, [cognium-ai](https://cognium.net), which can send code snippets to a configured model endpoint; use cognium-dev (or cognium-ai with AI-assisted mode off and no remote engine configured) for air-gapped scanning.
467
461
 
468
462
  ## Benchmark Results
469
463
 
package/dist/cli.js CHANGED
@@ -5515,6 +5515,14 @@ function csharpBareName(node) {
5515
5515
  }
5516
5516
  return getNodeText(node);
5517
5517
  }
5518
+ function csharpCreatedSimpleType(creation) {
5519
+ const typeNode = creation.childForFieldName("type");
5520
+ if (!typeNode)
5521
+ return null;
5522
+ const bare = csharpBareName(typeNode).replace(/<[^>]*>/g, "");
5523
+ const simple = bare.split(".").pop()?.trim() ?? "";
5524
+ return simple && simple !== "var" ? simple : null;
5525
+ }
5518
5526
  function extractCSharpCalls(tree, cache) {
5519
5527
  const calls = [];
5520
5528
  const typeMap = buildCSharpReceiverTypeMap(tree, cache);
@@ -5523,11 +5531,15 @@ function extractCSharpCalls(tree, cache) {
5523
5531
  const fn = inv.childForFieldName("function");
5524
5532
  let methodName = "unknown";
5525
5533
  let receiver = null;
5534
+ let receiverTypeFromCreation = null;
5526
5535
  if (fn?.type === "member_access_expression") {
5527
5536
  const nameNode = fn.childForFieldName("name");
5528
5537
  const exprNode = fn.childForFieldName("expression");
5529
5538
  methodName = nameNode ? csharpBareName(nameNode) : "unknown";
5530
5539
  receiver = exprNode ? getNodeText(exprNode) : null;
5540
+ if (exprNode?.type === "object_creation_expression") {
5541
+ receiverTypeFromCreation = csharpCreatedSimpleType(exprNode);
5542
+ }
5531
5543
  } else if (fn) {
5532
5544
  methodName = csharpBareName(fn);
5533
5545
  }
@@ -5545,7 +5557,7 @@ function extractCSharpCalls(tree, cache) {
5545
5557
  calls.push({
5546
5558
  method_name: methodName,
5547
5559
  receiver,
5548
- receiver_type: receiver ? typeMap.get(receiver) ?? null : null,
5560
+ receiver_type: receiverTypeFromCreation ?? (receiver ? typeMap.get(receiver) ?? null : null),
5549
5561
  receiver_type_fqn: null,
5550
5562
  arguments: args2,
5551
5563
  location: { line: inv.startPosition.row + 1, column: inv.startPosition.column },
@@ -11410,6 +11422,7 @@ var DEFAULT_SOURCES = [
11410
11422
  { method: "getFileName", class: "MimeBodyPart", type: "file_input", severity: "high", return_tainted: true },
11411
11423
  { method: "getDisposition", class: "Part", type: "file_input", severity: "medium", return_tainted: true },
11412
11424
  { method: "getName", class: "ZipEntry", type: "file_input", severity: "high", return_tainted: true },
11425
+ { method: "getName", class: "JarEntry", type: "file_input", severity: "high", return_tainted: true },
11413
11426
  { method: "getName", class: "ZipArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
11414
11427
  { method: "getName", class: "TarArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
11415
11428
  { method: "getName", class: "ArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
@@ -11858,7 +11871,7 @@ var DEFAULT_SINKS = [
11858
11871
  { method: "checkout", class: "SCM", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
11859
11872
  { method: "retrieve", class: "LibraryAdder", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
11860
11873
  { method: "add", class: "LibraryAdder", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
11861
- { method: "CpsFlowExecution", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
11874
+ { method: "CpsFlowExecution", class: "constructor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
11862
11875
  { method: "CpsFlowDefinition", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
11863
11876
  { method: "FlowExecution", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
11864
11877
  { method: "processControlCommand", class: "TransportConnection", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
@@ -14009,12 +14022,14 @@ function isInterproceduralTaintableType(typeName, language) {
14009
14022
  function isParameterizedQueryCall(call, pattern) {
14010
14023
  if (pattern.type !== "sql_injection")
14011
14024
  return false;
14012
- const queryArg = call.arguments.find((a) => a.position === 0);
14025
+ const queryPos = pattern.arg_positions.length > 0 ? Math.min(...pattern.arg_positions) : 0;
14026
+ const queryArg = call.arguments.find((a) => a.position === queryPos);
14013
14027
  if (queryArg) {
14014
14028
  const queryText = queryArg.literal ?? queryArg.expression ?? "";
14015
14029
  const hasPlaceholders = /(\?(?:\s|,|$|\))|\$\d+|:\w+|%s)/.test(queryText);
14016
14030
  const hasConcatenation = /\+\s*[^+]/.test(queryText) || queryText.includes("${");
14017
- if (hasPlaceholders && !hasConcatenation && call.arguments.length >= 2) {
14031
+ const hasBoundArg = queryPos === 0 ? call.arguments.length >= 2 : call.arguments.some((a) => a.position > queryPos);
14032
+ if (hasPlaceholders && !hasConcatenation && hasBoundArg) {
14018
14033
  return true;
14019
14034
  }
14020
14035
  }
@@ -14757,6 +14772,17 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
14757
14772
  }
14758
14773
  return false;
14759
14774
  }
14775
+ function isArgumentlessJsResponseWriter(call, pattern, language) {
14776
+ if (pattern.type !== "xss" || pattern.class !== "Response")
14777
+ return false;
14778
+ if (language !== "javascript" && language !== "typescript")
14779
+ return false;
14780
+ const positions = pattern.arg_positions;
14781
+ if (!positions || positions.length === 0)
14782
+ return false;
14783
+ const argc = call.arguments?.length ?? 0;
14784
+ return positions.every((pos) => pos >= argc);
14785
+ }
14760
14786
  function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types) {
14761
14787
  const patternsForLanguage = language === undefined ? patterns : patterns.filter((p) => sinkPatternAppliesTo(p, language));
14762
14788
  const sinkMap = new Map;
@@ -14817,6 +14843,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
14817
14843
  continue;
14818
14844
  }
14819
14845
  }
14846
+ if (isArgumentlessJsResponseWriter(call, pattern, language)) {
14847
+ continue;
14848
+ }
14820
14849
  if ((pattern.type === "command_injection" || pattern.type === "sql_injection") && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
14821
14850
  continue;
14822
14851
  }
@@ -24259,6 +24288,17 @@ class VuePlugin extends BaseLanguagePlugin {
24259
24288
  }
24260
24289
 
24261
24290
  // ../circle-ir/dist/languages/plugins/go.js
24291
+ function sqlSinks(cls, methods) {
24292
+ return methods.map(([method, argPositions]) => ({
24293
+ method,
24294
+ class: cls,
24295
+ type: "sql_injection",
24296
+ cwe: "CWE-89",
24297
+ severity: "critical",
24298
+ argPositions
24299
+ }));
24300
+ }
24301
+
24262
24302
  class GoPlugin extends BaseLanguagePlugin {
24263
24303
  id = "go";
24264
24304
  name = "Go";
@@ -24443,6 +24483,32 @@ class GoPlugin extends BaseLanguagePlugin {
24443
24483
  severity: "critical",
24444
24484
  argPositions: [0]
24445
24485
  },
24486
+ ...sqlSinks("DB", [
24487
+ ["QueryContext", [1]],
24488
+ ["QueryRowContext", [1]],
24489
+ ["ExecContext", [1]],
24490
+ ["Prepare", [0]],
24491
+ ["PrepareContext", [1]]
24492
+ ]),
24493
+ ...sqlSinks("Tx", [
24494
+ ["Exec", [0]],
24495
+ ["QueryRow", [0]],
24496
+ ["QueryContext", [1]],
24497
+ ["QueryRowContext", [1]],
24498
+ ["ExecContext", [1]],
24499
+ ["Prepare", [0]],
24500
+ ["PrepareContext", [1]]
24501
+ ]),
24502
+ ...sqlSinks("Conn", [
24503
+ ["Query", [0]],
24504
+ ["QueryRow", [0]],
24505
+ ["Exec", [0]],
24506
+ ["QueryContext", [1]],
24507
+ ["QueryRowContext", [1]],
24508
+ ["ExecContext", [1]],
24509
+ ["Prepare", [0]],
24510
+ ["PrepareContext", [1]]
24511
+ ]),
24446
24512
  {
24447
24513
  method: "Command",
24448
24514
  class: "exec",
@@ -39947,6 +40013,8 @@ function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourc
39947
40013
  "QueryRowContext",
39948
40014
  "Exec",
39949
40015
  "ExecContext",
40016
+ "Prepare",
40017
+ "PrepareContext",
39950
40018
  "EscapeString",
39951
40019
  "HTMLEscapeString",
39952
40020
  "JSEscapeString",
@@ -44880,7 +44948,7 @@ function isGeneratedFile(file) {
44880
44948
  var PROVIDER_PATTERNS = [
44881
44949
  {
44882
44950
  name: "AWS access key",
44883
- regex: /\bAKIA[0-9A-Z]{16}\b/,
44951
+ regex: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/,
44884
44952
  severity: "critical",
44885
44953
  level: "error",
44886
44954
  fix: "Rotate the AWS access key immediately and move it to an environment variable or AWS Secrets Manager."
@@ -44948,6 +45016,20 @@ var PROVIDER_PATTERNS = [
44948
45016
  level: "warning",
44949
45017
  fix: "Publishable keys are not secret but should still not be checked in to back-end source files; verify front-end vs back-end context."
44950
45018
  },
45019
+ {
45020
+ name: "Stripe test secret key",
45021
+ regex: /\bsk_test_[A-Za-z0-9]{24,}\b/,
45022
+ severity: "high",
45023
+ level: "warning",
45024
+ fix: "Rotate the Stripe test secret key in the Stripe Dashboard and load it from a secrets manager."
45025
+ },
45026
+ {
45027
+ name: "Stripe test restricted key",
45028
+ regex: /\brk_test_[A-Za-z0-9]{24,}\b/,
45029
+ severity: "high",
45030
+ level: "warning",
45031
+ fix: "Rotate the Stripe test restricted key in the Stripe Dashboard and load it from a secrets manager."
45032
+ },
44951
45033
  {
44952
45034
  name: "OpenAI API key",
44953
45035
  regex: /\bsk-[A-Za-z0-9]{48}\b/,
@@ -45292,7 +45374,7 @@ class ScanSecretsPass {
45292
45374
  const m = pattern.regex.exec(lineText);
45293
45375
  if (!m)
45294
45376
  continue;
45295
- if ((pattern.name === "Stripe live restricted key" || pattern.name === "GitLab personal access token") && PLACEHOLDER_RE.test(m[0])) {
45377
+ if ((pattern.name === "Stripe live restricted key" || pattern.name === "Stripe test secret key" || pattern.name === "Stripe test restricted key" || pattern.name === "GitLab personal access token") && PLACEHOLDER_RE.test(m[0])) {
45296
45378
  continue;
45297
45379
  }
45298
45380
  if (pattern.name === "PEM private key" && !pemHasInlineBody(lines, i2)) {
@@ -50396,6 +50478,27 @@ function longestLineLength(code) {
50396
50478
  return max;
50397
50479
  }
50398
50480
  var MAX_ANALYZABLE_LINE_LENGTH = 1e4;
50481
+ function neutralizeOverlongLines(code) {
50482
+ if (longestLineLength(code) <= MAX_ANALYZABLE_LINE_LENGTH) {
50483
+ return { code, dropped: 0 };
50484
+ }
50485
+ let out2 = "";
50486
+ let start2 = 0;
50487
+ let dropped = 0;
50488
+ for (let i2 = 0;i2 <= code.length; i2++) {
50489
+ if (i2 !== code.length && code.charCodeAt(i2) !== 10)
50490
+ continue;
50491
+ if (i2 - start2 > MAX_ANALYZABLE_LINE_LENGTH)
50492
+ dropped++;
50493
+ else if (i2 > start2)
50494
+ out2 += code.slice(start2, i2);
50495
+ if (i2 < code.length)
50496
+ out2 += `
50497
+ `;
50498
+ start2 = i2 + 1;
50499
+ }
50500
+ return { code: out2, dropped };
50501
+ }
50399
50502
  async function analyze(code, filePath, language, options = {}) {
50400
50503
  if (!initialized) {
50401
50504
  await initAnalyzer(options);
@@ -50414,6 +50517,15 @@ async function analyze(code, filePath, language, options = {}) {
50414
50517
  parseGrammar = "tsx";
50415
50518
  }
50416
50519
  }
50520
+ const neutralized = neutralizeOverlongLines(code);
50521
+ if (neutralized.dropped > 0) {
50522
+ logger.warn("Neutralized over-long lines before analysis", {
50523
+ filePath,
50524
+ language,
50525
+ droppedLines: neutralized.dropped
50526
+ });
50527
+ code = neutralized.code;
50528
+ }
50417
50529
  logger.debug("Analyzing file", { filePath, language, parseGrammar, codeLength: code.length });
50418
50530
  const phaseTimingEnabled = globalThis.__circleIrPassTiming === true;
50419
50531
  const phaseStart = () => phaseTimingEnabled ? Date.now() : 0;
@@ -51986,7 +52098,7 @@ var colors = {
51986
52098
  };
51987
52099
 
51988
52100
  // src/version.ts
51989
- var version = "4.13.2";
52101
+ var version = "4.14.0";
51990
52102
 
51991
52103
  // src/formatters.ts
51992
52104
  var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
@@ -52447,6 +52559,21 @@ function formatSARIF(results, crossFileData, profileSummary) {
52447
52559
  }
52448
52560
  },
52449
52561
  results: generateSarifResults(results, crossFileData),
52562
+ ...crossFileData?.projectSizeBudgetExceeded ? {
52563
+ invocations: [
52564
+ {
52565
+ executionSuccessful: false,
52566
+ toolExecutionNotifications: [
52567
+ {
52568
+ level: "warning",
52569
+ message: {
52570
+ text: `Project size ceiling reached — analysed ${crossFileData.filesAnalysed ?? "?"} of ${crossFileData.filesTotal ?? "?"} files.`
52571
+ }
52572
+ }
52573
+ ]
52574
+ }
52575
+ ]
52576
+ } : {},
52450
52577
  ...profileSummary ? { properties: { projectProfile: profileSummary } } : {}
52451
52578
  }
52452
52579
  ]
@@ -52538,8 +52665,8 @@ function generateSarifResults(results, crossFileData) {
52538
52665
 
52539
52666
  // src/build-info.ts
52540
52667
  var buildInfo = {
52541
- gitSha: "3937b7b",
52542
- builtAt: "2026-10-08T19:28:03.582Z"
52668
+ gitSha: "c44e104",
52669
+ builtAt: "2026-10-11T06:19:30.837Z"
52543
52670
  };
52544
52671
 
52545
52672
  // src/utils/args.ts
@@ -52781,6 +52908,38 @@ function spinner(text) {
52781
52908
  }
52782
52909
 
52783
52910
  // src/cli.ts
52911
+ function collapseWs(s) {
52912
+ return s.replace(/\s+/g, " ").trim();
52913
+ }
52914
+ function fileMatches(suppFile, findingFile, basePath) {
52915
+ const wanted = suppFile.replace(/^\.\//, "");
52916
+ const relativeFile = relative4(basePath, findingFile) || findingFile;
52917
+ return wanted === relativeFile || wanted === findingFile;
52918
+ }
52919
+ function findingMatchesSuppression(supp, finding, basePath, lineAt) {
52920
+ if (supp.pass !== finding.pass && supp.pass !== finding.alsoPass)
52921
+ return false;
52922
+ if (supp.file && !fileMatches(supp.file, finding.file, basePath))
52923
+ return false;
52924
+ const snippet = supp.snippet ? collapseWs(supp.snippet) : "";
52925
+ if (snippet) {
52926
+ const fromFile = lineAt?.(finding.file, finding.line);
52927
+ const text = fromFile !== undefined ? fromFile : finding.lineText;
52928
+ return text !== undefined && collapseWs(text).includes(snippet);
52929
+ }
52930
+ if (supp.line !== undefined && supp.line !== finding.line)
52931
+ return false;
52932
+ return true;
52933
+ }
52934
+ function staleSuppressions(suppressions, matched) {
52935
+ return suppressions.filter((_, i2) => !matched.has(i2));
52936
+ }
52937
+ function reportStaleSuppressions(suppressions, matched) {
52938
+ for (const supp of staleSuppressions(suppressions, matched)) {
52939
+ const where = [supp.file, supp.snippet ? "snippet" : "", supp.line !== undefined && !supp.snippet ? `line ${supp.line}` : ""].filter(Boolean).join(" ");
52940
+ console.error(colors.dim(`Suppression for ${supp.pass}${where ? ` (${where})` : ""} matched no finding`));
52941
+ }
52942
+ }
52784
52943
  function loadConfig(profilePath) {
52785
52944
  const configPath = profilePath || "cognium.config.json";
52786
52945
  if (!existsSync(configPath)) {
@@ -52833,22 +52992,15 @@ function convertConfigToPassOptions(config) {
52833
52992
  }
52834
52993
  return { passOptions, disabledPasses };
52835
52994
  }
52836
- function applySuppressionsToResults(results, suppressions, basePath) {
52995
+ function applySuppressionsToResults(results, suppressions, basePath, match) {
52837
52996
  if (suppressions.length === 0)
52838
52997
  return results;
52839
52998
  return results.map((result) => {
52840
- const relativeFile = relative4(basePath, result.file) || result.file;
52841
52999
  const filteredVulns = result.vulnerabilities.filter((vuln) => {
52842
- for (const supp of suppressions) {
52843
- if (supp.pass !== vuln.type)
52844
- continue;
52845
- if (supp.file) {
52846
- const suppFile = supp.file.replace(/^\.\//, "");
52847
- if (suppFile !== relativeFile && suppFile !== result.file)
52848
- continue;
52849
- }
52850
- if (supp.line !== undefined && supp.line !== vuln.line)
53000
+ for (let i2 = 0;i2 < suppressions.length; i2++) {
53001
+ if (!findingMatchesSuppression(suppressions[i2], { pass: vuln.type, file: result.file, line: vuln.line }, basePath, match?.lineAt))
52851
53002
  continue;
53003
+ match?.matched?.add(i2);
52852
53004
  return false;
52853
53005
  }
52854
53006
  return true;
@@ -52856,22 +53008,20 @@ function applySuppressionsToResults(results, suppressions, basePath) {
52856
53008
  return { ...result, vulnerabilities: filteredVulns };
52857
53009
  });
52858
53010
  }
52859
- function applySuppressionsToTaintPaths(paths, suppressions, basePath) {
53011
+ function applySuppressionsToTaintPaths(paths, suppressions, basePath, match) {
52860
53012
  if (suppressions.length === 0)
52861
53013
  return paths;
52862
53014
  return paths.filter((path) => {
52863
- for (const supp of suppressions) {
52864
- const sinkType = path.sink.type;
52865
- if (supp.pass !== sinkType && supp.pass !== `cross-file-${sinkType}`)
52866
- continue;
52867
- if (supp.file) {
52868
- const suppFile = supp.file.replace(/^\.\//, "");
52869
- const relativeFile = relative4(basePath, path.sink.file) || path.sink.file;
52870
- if (suppFile !== relativeFile && suppFile !== path.sink.file)
52871
- continue;
52872
- }
52873
- if (supp.line !== undefined && supp.line !== path.sink.line)
52874
- continue;
53015
+ for (let i2 = 0;i2 < suppressions.length; i2++) {
53016
+ if (!findingMatchesSuppression(suppressions[i2], {
53017
+ pass: path.sink.type,
53018
+ alsoPass: `cross-file-${path.sink.type}`,
53019
+ file: path.sink.file,
53020
+ line: path.sink.line,
53021
+ lineText: path.sink.code
53022
+ }, basePath, match?.lineAt))
53023
+ continue;
53024
+ match?.matched?.add(i2);
52875
53025
  return false;
52876
53026
  }
52877
53027
  return true;
@@ -53394,14 +53544,35 @@ async function runScan(targetPath, options) {
53394
53544
  }
53395
53545
  if (suppressions.length > 0) {
53396
53546
  const beforeCount = results.reduce((sum, r) => sum + r.vulnerabilities.length, 0) + (crossFileData?.taintPaths.length ?? 0);
53397
- results = applySuppressionsToResults(results, suppressions, process.cwd());
53547
+ const matched = new Set;
53548
+ const needsLines = suppressions.some((s) => s.snippet && collapseWs(s.snippet).length > 0);
53549
+ const lineCache = new Map;
53550
+ const lineAt = needsLines ? (file, line) => {
53551
+ let lines = lineCache.get(file);
53552
+ if (lines === undefined) {
53553
+ try {
53554
+ lines = readFileSync(file, "utf-8").split(`
53555
+ `);
53556
+ } catch {
53557
+ lines = null;
53558
+ }
53559
+ lineCache.set(file, lines);
53560
+ }
53561
+ if (!lines || line < 1)
53562
+ return;
53563
+ return lines[line - 1];
53564
+ } : undefined;
53565
+ const match = { lineAt, matched };
53566
+ results = applySuppressionsToResults(results, suppressions, process.cwd(), match);
53398
53567
  if (crossFileData) {
53399
- crossFileData.taintPaths = applySuppressionsToTaintPaths(crossFileData.taintPaths, suppressions, process.cwd());
53568
+ crossFileData.taintPaths = applySuppressionsToTaintPaths(crossFileData.taintPaths, suppressions, process.cwd(), match);
53400
53569
  }
53401
53570
  const afterCount = results.reduce((sum, r) => sum + r.vulnerabilities.length, 0) + (crossFileData?.taintPaths.length ?? 0);
53402
53571
  if (!options.quiet && beforeCount !== afterCount) {
53403
53572
  console.error(colors.dim(`Suppressed ${beforeCount - afterCount} finding(s) via config`));
53404
53573
  }
53574
+ if (!options.quiet)
53575
+ reportStaleSuppressions(suppressions, matched);
53405
53576
  }
53406
53577
  const severityOrder = ["low", "medium", "high", "critical"];
53407
53578
  if (options.severity) {
@@ -54034,7 +54205,9 @@ main().catch((error) => {
54034
54205
  process.exit(2);
54035
54206
  });
54036
54207
  export {
54208
+ staleSuppressions,
54037
54209
  resolveScanLanguage,
54210
+ reportStaleSuppressions,
54038
54211
  matchesGlob,
54039
54212
  loadConfig,
54040
54213
  isTestFile2 as isTestFile,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "4.13.2",
3
+ "version": "4.14.0",
4
4
  "description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
5
5
  "bin": {
6
6
  "cognium-dev": "dist/cli.js"
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "dependencies": {
67
67
  "@cognium/project-profile-detect": "1.1.2",
68
- "circle-ir": "4.13.2"
68
+ "circle-ir": "4.14.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^26.6.4",