cognium-dev 3.27.1 → 3.28.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 (2) hide show
  1. package/dist/cli.js +203 -182
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -3261,6 +3261,7 @@ var parserInitialized = false;
3261
3261
  var parserInitializing = null;
3262
3262
  var loadedLanguages = new Map;
3263
3263
  var loadingLanguages = new Map;
3264
+ var cachedParsers = new Map;
3264
3265
  var configuredLanguagePaths = {};
3265
3266
  var configuredLanguageModules = {};
3266
3267
  async function initParser(options = {}) {
@@ -3330,9 +3331,14 @@ async function loadLanguage(language, wasmPath) {
3330
3331
  return loadPromise;
3331
3332
  }
3332
3333
  async function createParser(language) {
3334
+ const cached = cachedParsers.get(language);
3335
+ if (cached) {
3336
+ return cached;
3337
+ }
3333
3338
  const lang = await loadLanguage(language);
3334
3339
  const parser = new Parser;
3335
3340
  parser.setLanguage(lang);
3341
+ cachedParsers.set(language, parser);
3336
3342
  return parser;
3337
3343
  }
3338
3344
  async function parse(code, language) {
@@ -3343,6 +3349,13 @@ async function parse(code, language) {
3343
3349
  }
3344
3350
  return tree;
3345
3351
  }
3352
+ function disposeTree(tree) {
3353
+ if (!tree)
3354
+ return;
3355
+ try {
3356
+ tree.delete();
3357
+ } catch {}
3358
+ }
3346
3359
  function walkTree(node, visitor) {
3347
3360
  visitor(node);
3348
3361
  for (let i2 = 0;i2 < node.childCount; i2++) {
@@ -25761,194 +25774,202 @@ async function analyze(code, filePath, language, options = {}) {
25761
25774
  }
25762
25775
  logger.debug("Analyzing file", { filePath, language, codeLength: code.length });
25763
25776
  const tree = await parse(code, language);
25764
- logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
25765
- const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
25766
- const meta = extractMeta(code, tree, filePath, language);
25767
- const types = extractTypes(tree, nodeCache, language);
25768
- const calls = extractCalls(tree, nodeCache, language);
25769
- const imports = extractImports(tree, language);
25770
- const exports = extractExports(types);
25771
- const cfg = buildCFG(tree, language);
25772
- const dfg = buildDFG(tree, nodeCache, language);
25773
- const graph = new CodeGraph({
25774
- meta,
25775
- types,
25776
- calls,
25777
- cfg,
25778
- dfg,
25779
- taint: { sources: [], sinks: [], sanitizers: [] },
25780
- imports,
25781
- exports,
25782
- unresolved: [],
25783
- enriched: {}
25784
- });
25785
- const config = options.taintConfig ?? getDefaultConfig();
25786
- const disabledPasses = new Set(options.disabledPasses ?? []);
25787
- const passOpts = options.passOptions ?? {};
25788
- const pipeline = new AnalysisPipeline;
25789
- pipeline.add(new TaintMatcherPass);
25790
- pipeline.add(new ConstantPropagationPass(tree));
25791
- pipeline.add(new LanguageSourcesPass);
25792
- pipeline.add(new SinkFilterPass);
25793
- pipeline.add(new TaintPropagationPass);
25794
- pipeline.add(new InterproceduralPass);
25795
- if (!disabledPasses.has("scan-secrets"))
25796
- pipeline.add(new ScanSecretsPass);
25797
- if (!disabledPasses.has("dead-code"))
25798
- pipeline.add(new DeadCodePass);
25799
- if (!disabledPasses.has("missing-await"))
25800
- pipeline.add(new MissingAwaitPass);
25801
- if (!disabledPasses.has("n-plus-one"))
25802
- pipeline.add(new NPlusOnePass);
25803
- if (!disabledPasses.has("missing-public-doc"))
25804
- pipeline.add(new MissingPublicDocPass);
25805
- if (!disabledPasses.has("todo-in-prod"))
25806
- pipeline.add(new TodoInProdPass);
25807
- if (!disabledPasses.has("string-concat-loop"))
25808
- pipeline.add(new StringConcatLoopPass);
25809
- if (!disabledPasses.has("sync-io-async"))
25810
- pipeline.add(new SyncIoAsyncPass);
25811
- if (!disabledPasses.has("unchecked-return"))
25812
- pipeline.add(new UncheckedReturnPass);
25813
- if (!disabledPasses.has("null-deref"))
25814
- pipeline.add(new NullDerefPass);
25815
- if (!disabledPasses.has("resource-leak"))
25816
- pipeline.add(new ResourceLeakPass);
25817
- if (!disabledPasses.has("variable-shadowing"))
25818
- pipeline.add(new VariableShadowingPass);
25819
- if (!disabledPasses.has("leaked-global"))
25820
- pipeline.add(new LeakedGlobalPass);
25821
- if (!disabledPasses.has("unused-variable"))
25822
- pipeline.add(new UnusedVariablePass);
25823
- if (!disabledPasses.has("dependency-fan-out"))
25824
- pipeline.add(new DependencyFanOutPass(passOpts.dependencyFanOut));
25825
- if (!disabledPasses.has("stale-doc-ref"))
25826
- pipeline.add(new StaleDocRefPass);
25827
- if (!disabledPasses.has("infinite-loop"))
25828
- pipeline.add(new InfiniteLoopPass);
25829
- if (!disabledPasses.has("deep-inheritance"))
25830
- pipeline.add(new DeepInheritancePass);
25831
- if (!disabledPasses.has("redundant-loop-computation"))
25832
- pipeline.add(new RedundantLoopPass);
25833
- if (!disabledPasses.has("unbounded-collection"))
25834
- pipeline.add(new UnboundedCollectionPass(passOpts.unboundedCollection));
25835
- if (!disabledPasses.has("serial-await"))
25836
- pipeline.add(new SerialAwaitPass);
25837
- if (!disabledPasses.has("react-inline-jsx"))
25838
- pipeline.add(new ReactInlineJsxPass);
25839
- if (!disabledPasses.has("swallowed-exception"))
25840
- pipeline.add(new SwallowedExceptionPass);
25841
- if (!disabledPasses.has("broad-catch"))
25842
- pipeline.add(new BroadCatchPass);
25843
- if (!disabledPasses.has("unhandled-exception"))
25844
- pipeline.add(new UnhandledExceptionPass);
25845
- if (!disabledPasses.has("double-close"))
25846
- pipeline.add(new DoubleClosePass);
25847
- if (!disabledPasses.has("use-after-close"))
25848
- pipeline.add(new UseAfterClosePass);
25849
- if (!disabledPasses.has("cleanup-verify"))
25850
- pipeline.add(new CleanupVerifyPass);
25851
- if (!disabledPasses.has("missing-override"))
25852
- pipeline.add(new MissingOverridePass);
25853
- if (!disabledPasses.has("unused-interface-method"))
25854
- pipeline.add(new UnusedInterfaceMethodPass);
25855
- if (!disabledPasses.has("blocking-main-thread"))
25856
- pipeline.add(new BlockingMainThreadPass);
25857
- if (!disabledPasses.has("excessive-allocation"))
25858
- pipeline.add(new ExcessiveAllocationPass);
25859
- if (!disabledPasses.has("missing-stream"))
25860
- pipeline.add(new MissingStreamPass);
25861
- if (!disabledPasses.has("god-class"))
25862
- pipeline.add(new GodClassPass);
25863
- if (!disabledPasses.has("naming-convention"))
25864
- pipeline.add(new NamingConventionPass(passOpts.namingConvention));
25865
- if (!disabledPasses.has("security-headers"))
25866
- pipeline.add(new SecurityHeadersPass(passOpts.securityHeaders));
25867
- const { results, findings } = pipeline.run(graph, code, language, config);
25868
- const sinkFilter = results.get("sink-filter");
25869
- const interProc = results.get("interprocedural");
25870
- const taint = {
25871
- sources: sinkFilter.sources,
25872
- sinks: [...sinkFilter.sinks, ...interProc.additionalSinks],
25873
- sanitizers: sinkFilter.sanitizers,
25874
- flows: interProc.additionalFlows,
25875
- interprocedural: interProc.interprocedural
25876
- };
25877
- const unresolved = detectUnresolved(calls, types, dfg);
25878
- const enriched = buildEnriched(types, calls, taint.sources, taint.sinks);
25879
- const metricValues = new MetricRunner().run({ meta, types, calls, cfg, dfg, taint, imports, exports, unresolved, enriched }, code, language);
25880
- logger.debug("Analysis complete", {
25881
- filePath,
25882
- finalSources: taint.sources.length,
25883
- finalSinks: taint.sinks.length,
25884
- flows: taint.flows?.length ?? 0,
25885
- unresolvedItems: unresolved.length
25886
- });
25887
- return {
25888
- meta,
25889
- types,
25890
- calls,
25891
- cfg,
25892
- dfg,
25893
- taint,
25894
- imports,
25895
- exports,
25896
- unresolved,
25897
- enriched,
25898
- findings: findings.length > 0 ? findings : undefined,
25899
- metrics: { file: filePath, metrics: metricValues }
25900
- };
25777
+ try {
25778
+ logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
25779
+ const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
25780
+ const meta = extractMeta(code, tree, filePath, language);
25781
+ const types = extractTypes(tree, nodeCache, language);
25782
+ const calls = extractCalls(tree, nodeCache, language);
25783
+ const imports = extractImports(tree, language);
25784
+ const exports = extractExports(types);
25785
+ const cfg = buildCFG(tree, language);
25786
+ const dfg = buildDFG(tree, nodeCache, language);
25787
+ const graph = new CodeGraph({
25788
+ meta,
25789
+ types,
25790
+ calls,
25791
+ cfg,
25792
+ dfg,
25793
+ taint: { sources: [], sinks: [], sanitizers: [] },
25794
+ imports,
25795
+ exports,
25796
+ unresolved: [],
25797
+ enriched: {}
25798
+ });
25799
+ const config = options.taintConfig ?? getDefaultConfig();
25800
+ const disabledPasses = new Set(options.disabledPasses ?? []);
25801
+ const passOpts = options.passOptions ?? {};
25802
+ const pipeline = new AnalysisPipeline;
25803
+ pipeline.add(new TaintMatcherPass);
25804
+ pipeline.add(new ConstantPropagationPass(tree));
25805
+ pipeline.add(new LanguageSourcesPass);
25806
+ pipeline.add(new SinkFilterPass);
25807
+ pipeline.add(new TaintPropagationPass);
25808
+ pipeline.add(new InterproceduralPass);
25809
+ if (!disabledPasses.has("scan-secrets"))
25810
+ pipeline.add(new ScanSecretsPass);
25811
+ if (!disabledPasses.has("dead-code"))
25812
+ pipeline.add(new DeadCodePass);
25813
+ if (!disabledPasses.has("missing-await"))
25814
+ pipeline.add(new MissingAwaitPass);
25815
+ if (!disabledPasses.has("n-plus-one"))
25816
+ pipeline.add(new NPlusOnePass);
25817
+ if (!disabledPasses.has("missing-public-doc"))
25818
+ pipeline.add(new MissingPublicDocPass);
25819
+ if (!disabledPasses.has("todo-in-prod"))
25820
+ pipeline.add(new TodoInProdPass);
25821
+ if (!disabledPasses.has("string-concat-loop"))
25822
+ pipeline.add(new StringConcatLoopPass);
25823
+ if (!disabledPasses.has("sync-io-async"))
25824
+ pipeline.add(new SyncIoAsyncPass);
25825
+ if (!disabledPasses.has("unchecked-return"))
25826
+ pipeline.add(new UncheckedReturnPass);
25827
+ if (!disabledPasses.has("null-deref"))
25828
+ pipeline.add(new NullDerefPass);
25829
+ if (!disabledPasses.has("resource-leak"))
25830
+ pipeline.add(new ResourceLeakPass);
25831
+ if (!disabledPasses.has("variable-shadowing"))
25832
+ pipeline.add(new VariableShadowingPass);
25833
+ if (!disabledPasses.has("leaked-global"))
25834
+ pipeline.add(new LeakedGlobalPass);
25835
+ if (!disabledPasses.has("unused-variable"))
25836
+ pipeline.add(new UnusedVariablePass);
25837
+ if (!disabledPasses.has("dependency-fan-out"))
25838
+ pipeline.add(new DependencyFanOutPass(passOpts.dependencyFanOut));
25839
+ if (!disabledPasses.has("stale-doc-ref"))
25840
+ pipeline.add(new StaleDocRefPass);
25841
+ if (!disabledPasses.has("infinite-loop"))
25842
+ pipeline.add(new InfiniteLoopPass);
25843
+ if (!disabledPasses.has("deep-inheritance"))
25844
+ pipeline.add(new DeepInheritancePass);
25845
+ if (!disabledPasses.has("redundant-loop-computation"))
25846
+ pipeline.add(new RedundantLoopPass);
25847
+ if (!disabledPasses.has("unbounded-collection"))
25848
+ pipeline.add(new UnboundedCollectionPass(passOpts.unboundedCollection));
25849
+ if (!disabledPasses.has("serial-await"))
25850
+ pipeline.add(new SerialAwaitPass);
25851
+ if (!disabledPasses.has("react-inline-jsx"))
25852
+ pipeline.add(new ReactInlineJsxPass);
25853
+ if (!disabledPasses.has("swallowed-exception"))
25854
+ pipeline.add(new SwallowedExceptionPass);
25855
+ if (!disabledPasses.has("broad-catch"))
25856
+ pipeline.add(new BroadCatchPass);
25857
+ if (!disabledPasses.has("unhandled-exception"))
25858
+ pipeline.add(new UnhandledExceptionPass);
25859
+ if (!disabledPasses.has("double-close"))
25860
+ pipeline.add(new DoubleClosePass);
25861
+ if (!disabledPasses.has("use-after-close"))
25862
+ pipeline.add(new UseAfterClosePass);
25863
+ if (!disabledPasses.has("cleanup-verify"))
25864
+ pipeline.add(new CleanupVerifyPass);
25865
+ if (!disabledPasses.has("missing-override"))
25866
+ pipeline.add(new MissingOverridePass);
25867
+ if (!disabledPasses.has("unused-interface-method"))
25868
+ pipeline.add(new UnusedInterfaceMethodPass);
25869
+ if (!disabledPasses.has("blocking-main-thread"))
25870
+ pipeline.add(new BlockingMainThreadPass);
25871
+ if (!disabledPasses.has("excessive-allocation"))
25872
+ pipeline.add(new ExcessiveAllocationPass);
25873
+ if (!disabledPasses.has("missing-stream"))
25874
+ pipeline.add(new MissingStreamPass);
25875
+ if (!disabledPasses.has("god-class"))
25876
+ pipeline.add(new GodClassPass);
25877
+ if (!disabledPasses.has("naming-convention"))
25878
+ pipeline.add(new NamingConventionPass(passOpts.namingConvention));
25879
+ if (!disabledPasses.has("security-headers"))
25880
+ pipeline.add(new SecurityHeadersPass(passOpts.securityHeaders));
25881
+ const { results, findings } = pipeline.run(graph, code, language, config);
25882
+ const sinkFilter = results.get("sink-filter");
25883
+ const interProc = results.get("interprocedural");
25884
+ const taint = {
25885
+ sources: sinkFilter.sources,
25886
+ sinks: [...sinkFilter.sinks, ...interProc.additionalSinks],
25887
+ sanitizers: sinkFilter.sanitizers,
25888
+ flows: interProc.additionalFlows,
25889
+ interprocedural: interProc.interprocedural
25890
+ };
25891
+ const unresolved = detectUnresolved(calls, types, dfg);
25892
+ const enriched = buildEnriched(types, calls, taint.sources, taint.sinks);
25893
+ const metricValues = new MetricRunner().run({ meta, types, calls, cfg, dfg, taint, imports, exports, unresolved, enriched }, code, language);
25894
+ logger.debug("Analysis complete", {
25895
+ filePath,
25896
+ finalSources: taint.sources.length,
25897
+ finalSinks: taint.sinks.length,
25898
+ flows: taint.flows?.length ?? 0,
25899
+ unresolvedItems: unresolved.length
25900
+ });
25901
+ return {
25902
+ meta,
25903
+ types,
25904
+ calls,
25905
+ cfg,
25906
+ dfg,
25907
+ taint,
25908
+ imports,
25909
+ exports,
25910
+ unresolved,
25911
+ enriched,
25912
+ findings: findings.length > 0 ? findings : undefined,
25913
+ metrics: { file: filePath, metrics: metricValues }
25914
+ };
25915
+ } finally {
25916
+ disposeTree(tree);
25917
+ }
25901
25918
  }
25902
25919
  async function analyzeHtmlFile(code, filePath, options) {
25903
25920
  logger.debug("Analyzing HTML file", { filePath, codeLength: code.length });
25904
25921
  const tree = await parse(code, "html");
25905
- const meta = extractMeta(code, tree, filePath, "html");
25906
- const { scriptBlocks, eventHandlers } = extractHtmlContent(tree.rootNode);
25907
- logger.debug("HTML extraction", {
25908
- filePath,
25909
- inlineScripts: scriptBlocks.filter((b) => b.kind === "inline").length,
25910
- externalScripts: scriptBlocks.filter((b) => b.kind === "external-src").length,
25911
- eventHandlers: eventHandlers.length
25912
- });
25913
- const scriptResults = [];
25914
- for (const block of scriptBlocks) {
25915
- if (block.kind !== "inline" || !block.code.trim())
25916
- continue;
25917
- const scriptLang = block.scriptType === "ts" || block.scriptType === "typescript" || block.scriptType === "text/typescript" ? "typescript" : "javascript";
25918
- try {
25919
- const ir = await analyze(block.code, filePath, scriptLang, options);
25920
- scriptResults.push({ ir, lineOffset: block.lineOffset });
25921
- } catch (e) {
25922
- logger.warn("Failed to analyze script block", {
25923
- filePath,
25924
- lineOffset: block.lineOffset,
25925
- error: e instanceof Error ? e.message : String(e)
25926
- });
25922
+ try {
25923
+ const meta = extractMeta(code, tree, filePath, "html");
25924
+ const { scriptBlocks, eventHandlers } = extractHtmlContent(tree.rootNode);
25925
+ logger.debug("HTML extraction", {
25926
+ filePath,
25927
+ inlineScripts: scriptBlocks.filter((b) => b.kind === "inline").length,
25928
+ externalScripts: scriptBlocks.filter((b) => b.kind === "external-src").length,
25929
+ eventHandlers: eventHandlers.length
25930
+ });
25931
+ const scriptResults = [];
25932
+ for (const block of scriptBlocks) {
25933
+ if (block.kind !== "inline" || !block.code.trim())
25934
+ continue;
25935
+ const scriptLang = block.scriptType === "ts" || block.scriptType === "typescript" || block.scriptType === "text/typescript" ? "typescript" : "javascript";
25936
+ try {
25937
+ const ir = await analyze(block.code, filePath, scriptLang, options);
25938
+ scriptResults.push({ ir, lineOffset: block.lineOffset });
25939
+ } catch (e) {
25940
+ logger.warn("Failed to analyze script block", {
25941
+ filePath,
25942
+ lineOffset: block.lineOffset,
25943
+ error: e instanceof Error ? e.message : String(e)
25944
+ });
25945
+ }
25927
25946
  }
25928
- }
25929
- for (const handler of eventHandlers) {
25930
- const wrappedCode = `function __${handler.eventName}_handler() { ${handler.code} }`;
25931
- try {
25932
- const ir = await analyze(wrappedCode, filePath, "javascript", options);
25933
- scriptResults.push({ ir, lineOffset: handler.line });
25934
- } catch (e) {
25935
- logger.warn("Failed to analyze event handler", {
25936
- filePath,
25937
- eventName: handler.eventName,
25938
- line: handler.line,
25939
- error: e instanceof Error ? e.message : String(e)
25940
- });
25947
+ for (const handler of eventHandlers) {
25948
+ const wrappedCode = `function __${handler.eventName}_handler() { ${handler.code} }`;
25949
+ try {
25950
+ const ir = await analyze(wrappedCode, filePath, "javascript", options);
25951
+ scriptResults.push({ ir, lineOffset: handler.line });
25952
+ } catch (e) {
25953
+ logger.warn("Failed to analyze event handler", {
25954
+ filePath,
25955
+ eventName: handler.eventName,
25956
+ line: handler.line,
25957
+ error: e instanceof Error ? e.message : String(e)
25958
+ });
25959
+ }
25941
25960
  }
25961
+ const attributeFindings = runHtmlAttributeSecurityChecks(tree.rootNode, filePath);
25962
+ const result = mergeHtmlResults(meta, scriptResults, attributeFindings);
25963
+ logger.debug("HTML analysis complete", {
25964
+ filePath,
25965
+ scriptBlocks: scriptResults.length,
25966
+ attributeFindings: attributeFindings.length,
25967
+ totalFindings: result.findings?.length ?? 0
25968
+ });
25969
+ return result;
25970
+ } finally {
25971
+ disposeTree(tree);
25942
25972
  }
25943
- const attributeFindings = runHtmlAttributeSecurityChecks(tree.rootNode, filePath);
25944
- const result = mergeHtmlResults(meta, scriptResults, attributeFindings);
25945
- logger.debug("HTML analysis complete", {
25946
- filePath,
25947
- scriptBlocks: scriptResults.length,
25948
- attributeFindings: attributeFindings.length,
25949
- totalFindings: result.findings?.length ?? 0
25950
- });
25951
- return result;
25952
25973
  }
25953
25974
  async function analyzeProject(files, options = {}) {
25954
25975
  const fileAnalyses = [];
@@ -26038,7 +26059,7 @@ var colors = {
26038
26059
  };
26039
26060
 
26040
26061
  // src/version.ts
26041
- var version = "3.27.1";
26062
+ var version = "3.28.0";
26042
26063
 
26043
26064
  // src/formatters.ts
26044
26065
  var SINK_SEVERITY = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.27.1",
3
+ "version": "3.28.0",
4
4
  "description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -65,7 +65,7 @@
65
65
  "registry": "https://registry.npmjs.org/"
66
66
  },
67
67
  "dependencies": {
68
- "circle-ir": "^3.27.1"
68
+ "circle-ir": "^3.28.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",