circle-ir 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.
@@ -3997,6 +3997,7 @@ var parserInitialized = false;
3997
3997
  var parserInitializing = null;
3998
3998
  var loadedLanguages = /* @__PURE__ */ new Map();
3999
3999
  var loadingLanguages = /* @__PURE__ */ new Map();
4000
+ var cachedParsers = /* @__PURE__ */ new Map();
4000
4001
  var configuredLanguagePaths = {};
4001
4002
  var configuredLanguageModules = {};
4002
4003
  async function initParser(options = {}) {
@@ -4066,9 +4067,14 @@ async function loadLanguage(language, wasmPath) {
4066
4067
  return loadPromise;
4067
4068
  }
4068
4069
  async function createParser(language) {
4070
+ const cached = cachedParsers.get(language);
4071
+ if (cached) {
4072
+ return cached;
4073
+ }
4069
4074
  const lang = await loadLanguage(language);
4070
4075
  const parser = new Parser();
4071
4076
  parser.setLanguage(lang);
4077
+ cachedParsers.set(language, parser);
4072
4078
  return parser;
4073
4079
  }
4074
4080
  async function parse(code, language) {
@@ -4079,6 +4085,13 @@ async function parse(code, language) {
4079
4085
  }
4080
4086
  return tree;
4081
4087
  }
4088
+ function disposeTree(tree) {
4089
+ if (!tree) return;
4090
+ try {
4091
+ tree.delete();
4092
+ } catch {
4093
+ }
4094
+ }
4082
4095
  function walkTree(node, visitor) {
4083
4096
  visitor(node);
4084
4097
  for (let i2 = 0; i2 < node.childCount; i2++) {
@@ -26094,161 +26107,169 @@ async function analyze(code, filePath, language, options = {}) {
26094
26107
  }
26095
26108
  logger.debug("Analyzing file", { filePath, language, codeLength: code.length });
26096
26109
  const tree = await parse(code, language);
26097
- logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
26098
- const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
26099
- const meta = extractMeta(code, tree, filePath, language);
26100
- const types = extractTypes(tree, nodeCache, language);
26101
- const calls = extractCalls(tree, nodeCache, language);
26102
- const imports = extractImports(tree, language);
26103
- const exports = extractExports(types);
26104
- const cfg = buildCFG(tree, language);
26105
- const dfg = buildDFG(tree, nodeCache, language);
26106
- const graph = new CodeGraph({
26107
- meta,
26108
- types,
26109
- calls,
26110
- cfg,
26111
- dfg,
26112
- taint: { sources: [], sinks: [], sanitizers: [] },
26113
- imports,
26114
- exports,
26115
- unresolved: [],
26116
- enriched: {}
26117
- });
26118
- const config = options.taintConfig ?? getDefaultConfig();
26119
- const disabledPasses = new Set(options.disabledPasses ?? []);
26120
- const passOpts = options.passOptions ?? {};
26121
- const pipeline = new AnalysisPipeline();
26122
- pipeline.add(new TaintMatcherPass());
26123
- pipeline.add(new ConstantPropagationPass(tree));
26124
- pipeline.add(new LanguageSourcesPass());
26125
- pipeline.add(new SinkFilterPass());
26126
- pipeline.add(new TaintPropagationPass());
26127
- pipeline.add(new InterproceduralPass());
26128
- if (!disabledPasses.has("scan-secrets")) pipeline.add(new ScanSecretsPass());
26129
- if (!disabledPasses.has("dead-code")) pipeline.add(new DeadCodePass());
26130
- if (!disabledPasses.has("missing-await")) pipeline.add(new MissingAwaitPass());
26131
- if (!disabledPasses.has("n-plus-one")) pipeline.add(new NPlusOnePass());
26132
- if (!disabledPasses.has("missing-public-doc")) pipeline.add(new MissingPublicDocPass());
26133
- if (!disabledPasses.has("todo-in-prod")) pipeline.add(new TodoInProdPass());
26134
- if (!disabledPasses.has("string-concat-loop")) pipeline.add(new StringConcatLoopPass());
26135
- if (!disabledPasses.has("sync-io-async")) pipeline.add(new SyncIoAsyncPass());
26136
- if (!disabledPasses.has("unchecked-return")) pipeline.add(new UncheckedReturnPass());
26137
- if (!disabledPasses.has("null-deref")) pipeline.add(new NullDerefPass());
26138
- if (!disabledPasses.has("resource-leak")) pipeline.add(new ResourceLeakPass());
26139
- if (!disabledPasses.has("variable-shadowing")) pipeline.add(new VariableShadowingPass());
26140
- if (!disabledPasses.has("leaked-global")) pipeline.add(new LeakedGlobalPass());
26141
- if (!disabledPasses.has("unused-variable")) pipeline.add(new UnusedVariablePass());
26142
- if (!disabledPasses.has("dependency-fan-out")) pipeline.add(new DependencyFanOutPass(passOpts.dependencyFanOut));
26143
- if (!disabledPasses.has("stale-doc-ref")) pipeline.add(new StaleDocRefPass());
26144
- if (!disabledPasses.has("infinite-loop")) pipeline.add(new InfiniteLoopPass());
26145
- if (!disabledPasses.has("deep-inheritance")) pipeline.add(new DeepInheritancePass());
26146
- if (!disabledPasses.has("redundant-loop-computation")) pipeline.add(new RedundantLoopPass());
26147
- if (!disabledPasses.has("unbounded-collection")) pipeline.add(new UnboundedCollectionPass(passOpts.unboundedCollection));
26148
- if (!disabledPasses.has("serial-await")) pipeline.add(new SerialAwaitPass());
26149
- if (!disabledPasses.has("react-inline-jsx")) pipeline.add(new ReactInlineJsxPass());
26150
- if (!disabledPasses.has("swallowed-exception")) pipeline.add(new SwallowedExceptionPass());
26151
- if (!disabledPasses.has("broad-catch")) pipeline.add(new BroadCatchPass());
26152
- if (!disabledPasses.has("unhandled-exception")) pipeline.add(new UnhandledExceptionPass());
26153
- if (!disabledPasses.has("double-close")) pipeline.add(new DoubleClosePass());
26154
- if (!disabledPasses.has("use-after-close")) pipeline.add(new UseAfterClosePass());
26155
- if (!disabledPasses.has("cleanup-verify")) pipeline.add(new CleanupVerifyPass());
26156
- if (!disabledPasses.has("missing-override")) pipeline.add(new MissingOverridePass());
26157
- if (!disabledPasses.has("unused-interface-method")) pipeline.add(new UnusedInterfaceMethodPass());
26158
- if (!disabledPasses.has("blocking-main-thread")) pipeline.add(new BlockingMainThreadPass());
26159
- if (!disabledPasses.has("excessive-allocation")) pipeline.add(new ExcessiveAllocationPass());
26160
- if (!disabledPasses.has("missing-stream")) pipeline.add(new MissingStreamPass());
26161
- if (!disabledPasses.has("god-class")) pipeline.add(new GodClassPass());
26162
- if (!disabledPasses.has("naming-convention")) pipeline.add(new NamingConventionPass(passOpts.namingConvention));
26163
- if (!disabledPasses.has("security-headers")) pipeline.add(new SecurityHeadersPass(passOpts.securityHeaders));
26164
- const { results, findings } = pipeline.run(graph, code, language, config);
26165
- const sinkFilter = results.get("sink-filter");
26166
- const interProc = results.get("interprocedural");
26167
- const taint = {
26168
- sources: sinkFilter.sources,
26169
- sinks: [...sinkFilter.sinks, ...interProc.additionalSinks],
26170
- sanitizers: sinkFilter.sanitizers,
26171
- flows: interProc.additionalFlows,
26172
- interprocedural: interProc.interprocedural
26173
- };
26174
- const unresolved = detectUnresolved(calls, types, dfg);
26175
- const enriched = buildEnriched(types, calls, taint.sources, taint.sinks);
26176
- const metricValues = new MetricRunner().run(
26177
- { meta, types, calls, cfg, dfg, taint, imports, exports, unresolved, enriched },
26178
- code,
26179
- language
26180
- );
26181
- logger.debug("Analysis complete", {
26182
- filePath,
26183
- finalSources: taint.sources.length,
26184
- finalSinks: taint.sinks.length,
26185
- flows: taint.flows?.length ?? 0,
26186
- unresolvedItems: unresolved.length
26187
- });
26188
- return {
26189
- meta,
26190
- types,
26191
- calls,
26192
- cfg,
26193
- dfg,
26194
- taint,
26195
- imports,
26196
- exports,
26197
- unresolved,
26198
- enriched,
26199
- findings: findings.length > 0 ? findings : void 0,
26200
- metrics: { file: filePath, metrics: metricValues }
26201
- };
26110
+ try {
26111
+ logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
26112
+ const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
26113
+ const meta = extractMeta(code, tree, filePath, language);
26114
+ const types = extractTypes(tree, nodeCache, language);
26115
+ const calls = extractCalls(tree, nodeCache, language);
26116
+ const imports = extractImports(tree, language);
26117
+ const exports = extractExports(types);
26118
+ const cfg = buildCFG(tree, language);
26119
+ const dfg = buildDFG(tree, nodeCache, language);
26120
+ const graph = new CodeGraph({
26121
+ meta,
26122
+ types,
26123
+ calls,
26124
+ cfg,
26125
+ dfg,
26126
+ taint: { sources: [], sinks: [], sanitizers: [] },
26127
+ imports,
26128
+ exports,
26129
+ unresolved: [],
26130
+ enriched: {}
26131
+ });
26132
+ const config = options.taintConfig ?? getDefaultConfig();
26133
+ const disabledPasses = new Set(options.disabledPasses ?? []);
26134
+ const passOpts = options.passOptions ?? {};
26135
+ const pipeline = new AnalysisPipeline();
26136
+ pipeline.add(new TaintMatcherPass());
26137
+ pipeline.add(new ConstantPropagationPass(tree));
26138
+ pipeline.add(new LanguageSourcesPass());
26139
+ pipeline.add(new SinkFilterPass());
26140
+ pipeline.add(new TaintPropagationPass());
26141
+ pipeline.add(new InterproceduralPass());
26142
+ if (!disabledPasses.has("scan-secrets")) pipeline.add(new ScanSecretsPass());
26143
+ if (!disabledPasses.has("dead-code")) pipeline.add(new DeadCodePass());
26144
+ if (!disabledPasses.has("missing-await")) pipeline.add(new MissingAwaitPass());
26145
+ if (!disabledPasses.has("n-plus-one")) pipeline.add(new NPlusOnePass());
26146
+ if (!disabledPasses.has("missing-public-doc")) pipeline.add(new MissingPublicDocPass());
26147
+ if (!disabledPasses.has("todo-in-prod")) pipeline.add(new TodoInProdPass());
26148
+ if (!disabledPasses.has("string-concat-loop")) pipeline.add(new StringConcatLoopPass());
26149
+ if (!disabledPasses.has("sync-io-async")) pipeline.add(new SyncIoAsyncPass());
26150
+ if (!disabledPasses.has("unchecked-return")) pipeline.add(new UncheckedReturnPass());
26151
+ if (!disabledPasses.has("null-deref")) pipeline.add(new NullDerefPass());
26152
+ if (!disabledPasses.has("resource-leak")) pipeline.add(new ResourceLeakPass());
26153
+ if (!disabledPasses.has("variable-shadowing")) pipeline.add(new VariableShadowingPass());
26154
+ if (!disabledPasses.has("leaked-global")) pipeline.add(new LeakedGlobalPass());
26155
+ if (!disabledPasses.has("unused-variable")) pipeline.add(new UnusedVariablePass());
26156
+ if (!disabledPasses.has("dependency-fan-out")) pipeline.add(new DependencyFanOutPass(passOpts.dependencyFanOut));
26157
+ if (!disabledPasses.has("stale-doc-ref")) pipeline.add(new StaleDocRefPass());
26158
+ if (!disabledPasses.has("infinite-loop")) pipeline.add(new InfiniteLoopPass());
26159
+ if (!disabledPasses.has("deep-inheritance")) pipeline.add(new DeepInheritancePass());
26160
+ if (!disabledPasses.has("redundant-loop-computation")) pipeline.add(new RedundantLoopPass());
26161
+ if (!disabledPasses.has("unbounded-collection")) pipeline.add(new UnboundedCollectionPass(passOpts.unboundedCollection));
26162
+ if (!disabledPasses.has("serial-await")) pipeline.add(new SerialAwaitPass());
26163
+ if (!disabledPasses.has("react-inline-jsx")) pipeline.add(new ReactInlineJsxPass());
26164
+ if (!disabledPasses.has("swallowed-exception")) pipeline.add(new SwallowedExceptionPass());
26165
+ if (!disabledPasses.has("broad-catch")) pipeline.add(new BroadCatchPass());
26166
+ if (!disabledPasses.has("unhandled-exception")) pipeline.add(new UnhandledExceptionPass());
26167
+ if (!disabledPasses.has("double-close")) pipeline.add(new DoubleClosePass());
26168
+ if (!disabledPasses.has("use-after-close")) pipeline.add(new UseAfterClosePass());
26169
+ if (!disabledPasses.has("cleanup-verify")) pipeline.add(new CleanupVerifyPass());
26170
+ if (!disabledPasses.has("missing-override")) pipeline.add(new MissingOverridePass());
26171
+ if (!disabledPasses.has("unused-interface-method")) pipeline.add(new UnusedInterfaceMethodPass());
26172
+ if (!disabledPasses.has("blocking-main-thread")) pipeline.add(new BlockingMainThreadPass());
26173
+ if (!disabledPasses.has("excessive-allocation")) pipeline.add(new ExcessiveAllocationPass());
26174
+ if (!disabledPasses.has("missing-stream")) pipeline.add(new MissingStreamPass());
26175
+ if (!disabledPasses.has("god-class")) pipeline.add(new GodClassPass());
26176
+ if (!disabledPasses.has("naming-convention")) pipeline.add(new NamingConventionPass(passOpts.namingConvention));
26177
+ if (!disabledPasses.has("security-headers")) pipeline.add(new SecurityHeadersPass(passOpts.securityHeaders));
26178
+ const { results, findings } = pipeline.run(graph, code, language, config);
26179
+ const sinkFilter = results.get("sink-filter");
26180
+ const interProc = results.get("interprocedural");
26181
+ const taint = {
26182
+ sources: sinkFilter.sources,
26183
+ sinks: [...sinkFilter.sinks, ...interProc.additionalSinks],
26184
+ sanitizers: sinkFilter.sanitizers,
26185
+ flows: interProc.additionalFlows,
26186
+ interprocedural: interProc.interprocedural
26187
+ };
26188
+ const unresolved = detectUnresolved(calls, types, dfg);
26189
+ const enriched = buildEnriched(types, calls, taint.sources, taint.sinks);
26190
+ const metricValues = new MetricRunner().run(
26191
+ { meta, types, calls, cfg, dfg, taint, imports, exports, unresolved, enriched },
26192
+ code,
26193
+ language
26194
+ );
26195
+ logger.debug("Analysis complete", {
26196
+ filePath,
26197
+ finalSources: taint.sources.length,
26198
+ finalSinks: taint.sinks.length,
26199
+ flows: taint.flows?.length ?? 0,
26200
+ unresolvedItems: unresolved.length
26201
+ });
26202
+ return {
26203
+ meta,
26204
+ types,
26205
+ calls,
26206
+ cfg,
26207
+ dfg,
26208
+ taint,
26209
+ imports,
26210
+ exports,
26211
+ unresolved,
26212
+ enriched,
26213
+ findings: findings.length > 0 ? findings : void 0,
26214
+ metrics: { file: filePath, metrics: metricValues }
26215
+ };
26216
+ } finally {
26217
+ disposeTree(tree);
26218
+ }
26202
26219
  }
26203
26220
  async function analyzeHtmlFile(code, filePath, options) {
26204
26221
  logger.debug("Analyzing HTML file", { filePath, codeLength: code.length });
26205
26222
  const tree = await parse(code, "html");
26206
- const meta = extractMeta(code, tree, filePath, "html");
26207
- const { scriptBlocks, eventHandlers } = extractHtmlContent(tree.rootNode);
26208
- logger.debug("HTML extraction", {
26209
- filePath,
26210
- inlineScripts: scriptBlocks.filter((b) => b.kind === "inline").length,
26211
- externalScripts: scriptBlocks.filter((b) => b.kind === "external-src").length,
26212
- eventHandlers: eventHandlers.length
26213
- });
26214
- const scriptResults = [];
26215
- for (const block of scriptBlocks) {
26216
- if (block.kind !== "inline" || !block.code.trim()) continue;
26217
- const scriptLang = block.scriptType === "ts" || block.scriptType === "typescript" || block.scriptType === "text/typescript" ? "typescript" : "javascript";
26218
- try {
26219
- const ir = await analyze(block.code, filePath, scriptLang, options);
26220
- scriptResults.push({ ir, lineOffset: block.lineOffset });
26221
- } catch (e) {
26222
- logger.warn("Failed to analyze script block", {
26223
- filePath,
26224
- lineOffset: block.lineOffset,
26225
- error: e instanceof Error ? e.message : String(e)
26226
- });
26223
+ try {
26224
+ const meta = extractMeta(code, tree, filePath, "html");
26225
+ const { scriptBlocks, eventHandlers } = extractHtmlContent(tree.rootNode);
26226
+ logger.debug("HTML extraction", {
26227
+ filePath,
26228
+ inlineScripts: scriptBlocks.filter((b) => b.kind === "inline").length,
26229
+ externalScripts: scriptBlocks.filter((b) => b.kind === "external-src").length,
26230
+ eventHandlers: eventHandlers.length
26231
+ });
26232
+ const scriptResults = [];
26233
+ for (const block of scriptBlocks) {
26234
+ if (block.kind !== "inline" || !block.code.trim()) continue;
26235
+ const scriptLang = block.scriptType === "ts" || block.scriptType === "typescript" || block.scriptType === "text/typescript" ? "typescript" : "javascript";
26236
+ try {
26237
+ const ir = await analyze(block.code, filePath, scriptLang, options);
26238
+ scriptResults.push({ ir, lineOffset: block.lineOffset });
26239
+ } catch (e) {
26240
+ logger.warn("Failed to analyze script block", {
26241
+ filePath,
26242
+ lineOffset: block.lineOffset,
26243
+ error: e instanceof Error ? e.message : String(e)
26244
+ });
26245
+ }
26227
26246
  }
26228
- }
26229
- for (const handler of eventHandlers) {
26230
- const wrappedCode = `function __${handler.eventName}_handler() { ${handler.code} }`;
26231
- try {
26232
- const ir = await analyze(wrappedCode, filePath, "javascript", options);
26233
- scriptResults.push({ ir, lineOffset: handler.line });
26234
- } catch (e) {
26235
- logger.warn("Failed to analyze event handler", {
26236
- filePath,
26237
- eventName: handler.eventName,
26238
- line: handler.line,
26239
- error: e instanceof Error ? e.message : String(e)
26240
- });
26247
+ for (const handler of eventHandlers) {
26248
+ const wrappedCode = `function __${handler.eventName}_handler() { ${handler.code} }`;
26249
+ try {
26250
+ const ir = await analyze(wrappedCode, filePath, "javascript", options);
26251
+ scriptResults.push({ ir, lineOffset: handler.line });
26252
+ } catch (e) {
26253
+ logger.warn("Failed to analyze event handler", {
26254
+ filePath,
26255
+ eventName: handler.eventName,
26256
+ line: handler.line,
26257
+ error: e instanceof Error ? e.message : String(e)
26258
+ });
26259
+ }
26241
26260
  }
26261
+ const attributeFindings = runHtmlAttributeSecurityChecks(tree.rootNode, filePath);
26262
+ const result = mergeHtmlResults(meta, scriptResults, attributeFindings);
26263
+ logger.debug("HTML analysis complete", {
26264
+ filePath,
26265
+ scriptBlocks: scriptResults.length,
26266
+ attributeFindings: attributeFindings.length,
26267
+ totalFindings: result.findings?.length ?? 0
26268
+ });
26269
+ return result;
26270
+ } finally {
26271
+ disposeTree(tree);
26242
26272
  }
26243
- const attributeFindings = runHtmlAttributeSecurityChecks(tree.rootNode, filePath);
26244
- const result = mergeHtmlResults(meta, scriptResults, attributeFindings);
26245
- logger.debug("HTML analysis complete", {
26246
- filePath,
26247
- scriptBlocks: scriptResults.length,
26248
- attributeFindings: attributeFindings.length,
26249
- totalFindings: result.findings?.length ?? 0
26250
- });
26251
- return result;
26252
26273
  }
26253
26274
  async function analyzeForAPI(code, filePath, language, options = {}) {
26254
26275
  const startTime = performance.now();
@@ -26258,75 +26279,79 @@ async function analyzeForAPI(code, filePath, language, options = {}) {
26258
26279
  const parseStart = performance.now();
26259
26280
  const tree = await parse(code, language);
26260
26281
  const parseTime = performance.now() - parseStart;
26261
- const analysisStart = performance.now();
26262
- const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
26263
- const types = extractTypes(tree, nodeCache, language);
26264
- const calls = extractCalls(tree, nodeCache, language);
26265
- const constPropResult = analyzeConstantPropagation(tree, code);
26266
- const config = options.taintConfig ?? getDefaultConfig();
26267
- const taint = analyzeTaint(calls, types, config);
26268
- let filteredSinks = taint.sinks.filter((sink) => !constPropResult.unreachableLines.has(sink.line));
26269
- filteredSinks = filterCleanVariableSinks(
26270
- filteredSinks,
26271
- calls,
26272
- constPropResult.tainted,
26273
- constPropResult.symbols,
26274
- void 0,
26275
- constPropResult.sanitizedVars,
26276
- constPropResult.synchronizedLines
26277
- );
26278
- filteredSinks = filterSanitizedSinks(filteredSinks, taint.sanitizers ?? [], calls);
26279
- let pythonTaintedVars = /* @__PURE__ */ new Map();
26280
- if (language === "python") {
26281
- pythonTaintedVars = buildPythonTaintedVars(code);
26282
- const pythonSanitizedVars = buildPythonSanitizedVars(code, pythonTaintedVars);
26283
- const sourceLines = code.split("\n");
26284
- filteredSinks = filteredSinks.filter((sink) => {
26285
- if (sink.type !== "xpath_injection") return true;
26286
- const sinkLineText = sourceLines[sink.line - 1] ?? "";
26287
- const taintedVarOnLine = [...pythonTaintedVars.keys()].find(
26288
- (v) => new RegExp(`\\b${v}\\b`).test(sinkLineText)
26289
- );
26290
- if (!taintedVarOnLine) return false;
26291
- if (pythonSanitizedVars.has(taintedVarOnLine)) return false;
26292
- if (new RegExp(`\\.xpath\\s*\\([^)]*\\b\\w+\\s*=\\s*\\b${taintedVarOnLine}\\b`).test(sinkLineText)) return false;
26293
- return true;
26294
- });
26295
- }
26296
- const vulnerabilities = findVulnerabilities(taint.sources, filteredSinks, calls, constPropResult);
26297
- if (language === "python") {
26298
- const trustViolations = findPythonTrustBoundaryViolations(code, pythonTaintedVars);
26299
- for (const v of trustViolations) {
26300
- const alreadyReported = vulnerabilities.some(
26301
- (existing) => existing.sink.line === v.sinkLine && existing.type === "trust_boundary"
26302
- );
26303
- if (!alreadyReported) {
26304
- vulnerabilities.push({
26305
- type: "trust_boundary",
26306
- cwe: "CWE-501",
26307
- severity: "medium",
26308
- source: { line: v.sourceLine, type: "http_param" },
26309
- sink: { line: v.sinkLine, type: "trust_boundary" },
26310
- confidence: 0.85
26311
- });
26282
+ try {
26283
+ const analysisStart = performance.now();
26284
+ const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
26285
+ const types = extractTypes(tree, nodeCache, language);
26286
+ const calls = extractCalls(tree, nodeCache, language);
26287
+ const constPropResult = analyzeConstantPropagation(tree, code);
26288
+ const config = options.taintConfig ?? getDefaultConfig();
26289
+ const taint = analyzeTaint(calls, types, config);
26290
+ let filteredSinks = taint.sinks.filter((sink) => !constPropResult.unreachableLines.has(sink.line));
26291
+ filteredSinks = filterCleanVariableSinks(
26292
+ filteredSinks,
26293
+ calls,
26294
+ constPropResult.tainted,
26295
+ constPropResult.symbols,
26296
+ void 0,
26297
+ constPropResult.sanitizedVars,
26298
+ constPropResult.synchronizedLines
26299
+ );
26300
+ filteredSinks = filterSanitizedSinks(filteredSinks, taint.sanitizers ?? [], calls);
26301
+ let pythonTaintedVars = /* @__PURE__ */ new Map();
26302
+ if (language === "python") {
26303
+ pythonTaintedVars = buildPythonTaintedVars(code);
26304
+ const pythonSanitizedVars = buildPythonSanitizedVars(code, pythonTaintedVars);
26305
+ const sourceLines = code.split("\n");
26306
+ filteredSinks = filteredSinks.filter((sink) => {
26307
+ if (sink.type !== "xpath_injection") return true;
26308
+ const sinkLineText = sourceLines[sink.line - 1] ?? "";
26309
+ const taintedVarOnLine = [...pythonTaintedVars.keys()].find(
26310
+ (v) => new RegExp(`\\b${v}\\b`).test(sinkLineText)
26311
+ );
26312
+ if (!taintedVarOnLine) return false;
26313
+ if (pythonSanitizedVars.has(taintedVarOnLine)) return false;
26314
+ if (new RegExp(`\\.xpath\\s*\\([^)]*\\b\\w+\\s*=\\s*\\b${taintedVarOnLine}\\b`).test(sinkLineText)) return false;
26315
+ return true;
26316
+ });
26317
+ }
26318
+ const vulnerabilities = findVulnerabilities(taint.sources, filteredSinks, calls, constPropResult);
26319
+ if (language === "python") {
26320
+ const trustViolations = findPythonTrustBoundaryViolations(code, pythonTaintedVars);
26321
+ for (const v of trustViolations) {
26322
+ const alreadyReported = vulnerabilities.some(
26323
+ (existing) => existing.sink.line === v.sinkLine && existing.type === "trust_boundary"
26324
+ );
26325
+ if (!alreadyReported) {
26326
+ vulnerabilities.push({
26327
+ type: "trust_boundary",
26328
+ cwe: "CWE-501",
26329
+ severity: "medium",
26330
+ source: { line: v.sourceLine, type: "http_param" },
26331
+ sink: { line: v.sinkLine, type: "trust_boundary" },
26332
+ confidence: 0.85
26333
+ });
26334
+ }
26312
26335
  }
26313
26336
  }
26337
+ const analysisTime = performance.now() - analysisStart;
26338
+ const totalTime = performance.now() - startTime;
26339
+ return {
26340
+ success: true,
26341
+ analysis: {
26342
+ sources: taint.sources,
26343
+ sinks: filteredSinks,
26344
+ vulnerabilities
26345
+ },
26346
+ meta: {
26347
+ parseTimeMs: Math.round(parseTime),
26348
+ analysisTimeMs: Math.round(analysisTime),
26349
+ totalTimeMs: Math.round(totalTime)
26350
+ }
26351
+ };
26352
+ } finally {
26353
+ disposeTree(tree);
26314
26354
  }
26315
- const analysisTime = performance.now() - analysisStart;
26316
- const totalTime = performance.now() - startTime;
26317
- return {
26318
- success: true,
26319
- analysis: {
26320
- sources: taint.sources,
26321
- sinks: filteredSinks,
26322
- vulnerabilities
26323
- },
26324
- meta: {
26325
- parseTimeMs: Math.round(parseTime),
26326
- analysisTimeMs: Math.round(analysisTime),
26327
- totalTimeMs: Math.round(totalTime)
26328
- }
26329
- };
26330
26355
  }
26331
26356
  function findVulnerabilities(sources, sinks, calls, constPropResult) {
26332
26357
  const vulnerabilities = [];
@@ -4062,6 +4062,7 @@ var parserInitialized = false;
4062
4062
  var parserInitializing = null;
4063
4063
  var loadedLanguages = /* @__PURE__ */ new Map();
4064
4064
  var loadingLanguages = /* @__PURE__ */ new Map();
4065
+ var cachedParsers = /* @__PURE__ */ new Map();
4065
4066
  var configuredLanguagePaths = {};
4066
4067
  var configuredLanguageModules = {};
4067
4068
  async function initParser(options = {}) {
@@ -4131,9 +4132,14 @@ async function loadLanguage(language, wasmPath) {
4131
4132
  return loadPromise;
4132
4133
  }
4133
4134
  async function createParser(language) {
4135
+ const cached = cachedParsers.get(language);
4136
+ if (cached) {
4137
+ return cached;
4138
+ }
4134
4139
  const lang = await loadLanguage(language);
4135
4140
  const parser = new Parser();
4136
4141
  parser.setLanguage(lang);
4142
+ cachedParsers.set(language, parser);
4137
4143
  return parser;
4138
4144
  }
4139
4145
  async function parse(code, language) {
@@ -4230,9 +4236,19 @@ function isLanguageLoaded(language) {
4230
4236
  return loadedLanguages.has(language);
4231
4237
  }
4232
4238
  function resetParser() {
4233
- parserInitialized = false;
4239
+ for (const parser of cachedParsers.values()) {
4240
+ try {
4241
+ parser.delete();
4242
+ } catch {
4243
+ }
4244
+ }
4245
+ cachedParsers.clear();
4234
4246
  loadedLanguages.clear();
4247
+ loadingLanguages.clear();
4235
4248
  configuredLanguagePaths = {};
4249
+ configuredLanguageModules = {};
4250
+ parserInitialized = false;
4251
+ parserInitializing = null;
4236
4252
  }
4237
4253
 
4238
4254
  // src/core/extractors/meta.ts
@@ -3997,6 +3997,7 @@ var parserInitialized = false;
3997
3997
  var parserInitializing = null;
3998
3998
  var loadedLanguages = /* @__PURE__ */ new Map();
3999
3999
  var loadingLanguages = /* @__PURE__ */ new Map();
4000
+ var cachedParsers = /* @__PURE__ */ new Map();
4000
4001
  var configuredLanguagePaths = {};
4001
4002
  var configuredLanguageModules = {};
4002
4003
  async function initParser(options = {}) {
@@ -4066,9 +4067,14 @@ async function loadLanguage(language, wasmPath) {
4066
4067
  return loadPromise;
4067
4068
  }
4068
4069
  async function createParser(language) {
4070
+ const cached = cachedParsers.get(language);
4071
+ if (cached) {
4072
+ return cached;
4073
+ }
4069
4074
  const lang = await loadLanguage(language);
4070
4075
  const parser = new Parser();
4071
4076
  parser.setLanguage(lang);
4077
+ cachedParsers.set(language, parser);
4072
4078
  return parser;
4073
4079
  }
4074
4080
  async function parse(code, language) {
@@ -4165,9 +4171,19 @@ function isLanguageLoaded(language) {
4165
4171
  return loadedLanguages.has(language);
4166
4172
  }
4167
4173
  function resetParser() {
4168
- parserInitialized = false;
4174
+ for (const parser of cachedParsers.values()) {
4175
+ try {
4176
+ parser.delete();
4177
+ } catch {
4178
+ }
4179
+ }
4180
+ cachedParsers.clear();
4169
4181
  loadedLanguages.clear();
4182
+ loadingLanguages.clear();
4170
4183
  configuredLanguagePaths = {};
4184
+ configuredLanguageModules = {};
4185
+ parserInitialized = false;
4186
+ parserInitializing = null;
4171
4187
  }
4172
4188
 
4173
4189
  // src/core/extractors/meta.ts
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Core module index - re-exports parser and extractors
3
3
  */
4
- export { initParser, loadLanguage, createParser, parse, walkTree, findNodes, findAncestor, getNodeText, collectAllNodes, getNodesFromCache, isInitialized, isLanguageLoaded, resetParser, type SupportedLanguage, type SyntaxNode, type Node, type NodeCache, type Language, type Tree, } from './parser.js';
4
+ export { initParser, loadLanguage, createParser, createFreshParser, parse, disposeTree, walkTree, findNodes, findAncestor, getNodeText, collectAllNodes, getNodesFromCache, isInitialized, isLanguageLoaded, resetParser, type SupportedLanguage, type SyntaxNode, type Node, type NodeCache, type Language, type Tree, } from './parser.js';
5
5
  export { extractMeta, extractTypes, extractCalls, extractImports, extractExports, buildCFG, buildDFG, } from './extractors/index.js';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EACL,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,KAAK,iBAAiB,EACtB,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,IAAI,GACV,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,cAAc,EACd,QAAQ,EACR,QAAQ,GACT,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EACL,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,WAAW,EACX,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,KAAK,iBAAiB,EACtB,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,IAAI,GACV,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,cAAc,EACd,QAAQ,EACR,QAAQ,GACT,MAAM,uBAAuB,CAAC"}
@@ -2,7 +2,7 @@
2
2
  * Core module index - re-exports parser and extractors
3
3
  */
4
4
  // Parser
5
- export { initParser, loadLanguage, createParser, parse, walkTree, findNodes, findAncestor, getNodeText, collectAllNodes, getNodesFromCache, isInitialized, isLanguageLoaded, resetParser, } from './parser.js';
5
+ export { initParser, loadLanguage, createParser, createFreshParser, parse, disposeTree, walkTree, findNodes, findAncestor, getNodeText, collectAllNodes, getNodesFromCache, isInitialized, isLanguageLoaded, resetParser, } from './parser.js';
6
6
  // Extractors
7
7
  export { extractMeta, extractTypes, extractCalls, extractImports, extractExports, buildCFG, buildDFG, } from './extractors/index.js';
8
8
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,SAAS;AACT,OAAO,EACL,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,KAAK,EACL,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,WAAW,GAOZ,MAAM,aAAa,CAAC;AAErB,aAAa;AACb,OAAO,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,cAAc,EACd,QAAQ,EACR,QAAQ,GACT,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,SAAS;AACT,OAAO,EACL,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,WAAW,EACX,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,WAAW,GAOZ,MAAM,aAAa,CAAC;AAErB,aAAa;AACb,OAAO,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,cAAc,EACd,QAAQ,EACR,QAAQ,GACT,MAAM,uBAAuB,CAAC"}