circle-ir-ai 1.1.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.
- package/CHANGELOG.md +105 -0
- package/LICENSE +15 -0
- package/README.md +336 -0
- package/dist/action-queue/aggregator.d.ts +40 -0
- package/dist/action-queue/aggregator.d.ts.map +1 -0
- package/dist/action-queue/aggregator.js +375 -0
- package/dist/action-queue/aggregator.js.map +1 -0
- package/dist/action-queue/index.d.ts +14 -0
- package/dist/action-queue/index.d.ts.map +1 -0
- package/dist/action-queue/index.js +17 -0
- package/dist/action-queue/index.js.map +1 -0
- package/dist/action-queue/queue.d.ts +74 -0
- package/dist/action-queue/queue.d.ts.map +1 -0
- package/dist/action-queue/queue.js +433 -0
- package/dist/action-queue/queue.js.map +1 -0
- package/dist/action-queue/types.d.ts +162 -0
- package/dist/action-queue/types.d.ts.map +1 -0
- package/dist/action-queue/types.js +44 -0
- package/dist/action-queue/types.js.map +1 -0
- package/dist/agents/enrichment-agent.d.ts +16 -0
- package/dist/agents/enrichment-agent.d.ts.map +1 -0
- package/dist/agents/enrichment-agent.js +102 -0
- package/dist/agents/enrichment-agent.js.map +1 -0
- package/dist/agents/index.d.ts +12 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +15 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/mastra/agents.d.ts +373 -0
- package/dist/agents/mastra/agents.d.ts.map +1 -0
- package/dist/agents/mastra/agents.js +347 -0
- package/dist/agents/mastra/agents.js.map +1 -0
- package/dist/agents/mastra/index.d.ts +12 -0
- package/dist/agents/mastra/index.d.ts.map +1 -0
- package/dist/agents/mastra/index.js +17 -0
- package/dist/agents/mastra/index.js.map +1 -0
- package/dist/agents/mastra/instance.d.ts +383 -0
- package/dist/agents/mastra/instance.d.ts.map +1 -0
- package/dist/agents/mastra/instance.js +37 -0
- package/dist/agents/mastra/instance.js.map +1 -0
- package/dist/agents/mastra/steps.d.ts +300 -0
- package/dist/agents/mastra/steps.d.ts.map +1 -0
- package/dist/agents/mastra/steps.js +468 -0
- package/dist/agents/mastra/steps.js.map +1 -0
- package/dist/agents/mastra/swarm.d.ts +106 -0
- package/dist/agents/mastra/swarm.d.ts.map +1 -0
- package/dist/agents/mastra/swarm.js +501 -0
- package/dist/agents/mastra/swarm.js.map +1 -0
- package/dist/agents/mastra/workflow.d.ts +81 -0
- package/dist/agents/mastra/workflow.d.ts.map +1 -0
- package/dist/agents/mastra/workflow.js +460 -0
- package/dist/agents/mastra/workflow.js.map +1 -0
- package/dist/agents/multi/agents/security.d.ts +29 -0
- package/dist/agents/multi/agents/security.d.ts.map +1 -0
- package/dist/agents/multi/agents/security.js +830 -0
- package/dist/agents/multi/agents/security.js.map +1 -0
- package/dist/agents/multi/extractor.d.ts +21 -0
- package/dist/agents/multi/extractor.d.ts.map +1 -0
- package/dist/agents/multi/extractor.js +483 -0
- package/dist/agents/multi/extractor.js.map +1 -0
- package/dist/agents/multi/index.d.ts +32 -0
- package/dist/agents/multi/index.d.ts.map +1 -0
- package/dist/agents/multi/index.js +34 -0
- package/dist/agents/multi/index.js.map +1 -0
- package/dist/agents/multi/runner.d.ts +79 -0
- package/dist/agents/multi/runner.d.ts.map +1 -0
- package/dist/agents/multi/runner.js +323 -0
- package/dist/agents/multi/runner.js.map +1 -0
- package/dist/agents/security-agent.d.ts +16 -0
- package/dist/agents/security-agent.d.ts.map +1 -0
- package/dist/agents/security-agent.js +299 -0
- package/dist/agents/security-agent.js.map +1 -0
- package/dist/agents/types.d.ts +373 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +14 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/agents/verification-agent.d.ts +23 -0
- package/dist/agents/verification-agent.d.ts.map +1 -0
- package/dist/agents/verification-agent.js +217 -0
- package/dist/agents/verification-agent.js.map +1 -0
- package/dist/agents/workflow.d.ts +30 -0
- package/dist/agents/workflow.d.ts.map +1 -0
- package/dist/agents/workflow.js +79 -0
- package/dist/agents/workflow.js.map +1 -0
- package/dist/analysis/enriched.d.ts +16 -0
- package/dist/analysis/enriched.d.ts.map +1 -0
- package/dist/analysis/enriched.js +297 -0
- package/dist/analysis/enriched.js.map +1 -0
- package/dist/analysis/llm-correlated-predicates.d.ts +80 -0
- package/dist/analysis/llm-correlated-predicates.d.ts.map +1 -0
- package/dist/analysis/llm-correlated-predicates.js +255 -0
- package/dist/analysis/llm-correlated-predicates.js.map +1 -0
- package/dist/analysis/llm-cross-file-taint.d.ts +86 -0
- package/dist/analysis/llm-cross-file-taint.d.ts.map +1 -0
- package/dist/analysis/llm-cross-file-taint.js +264 -0
- package/dist/analysis/llm-cross-file-taint.js.map +1 -0
- package/dist/analysis/pattern-discovery.d.ts +79 -0
- package/dist/analysis/pattern-discovery.d.ts.map +1 -0
- package/dist/analysis/pattern-discovery.js +447 -0
- package/dist/analysis/pattern-discovery.js.map +1 -0
- package/dist/cache/file-cache.d.ts +89 -0
- package/dist/cache/file-cache.d.ts.map +1 -0
- package/dist/cache/file-cache.js +208 -0
- package/dist/cache/file-cache.js.map +1 -0
- package/dist/cache/index.d.ts +6 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +5 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cli/args.d.ts +52 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +422 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/colors.d.ts +31 -0
- package/dist/cli/colors.d.ts.map +1 -0
- package/dist/cli/colors.js +80 -0
- package/dist/cli/colors.js.map +1 -0
- package/dist/cli/commands/analyze-skill.d.ts +33 -0
- package/dist/cli/commands/analyze-skill.d.ts.map +1 -0
- package/dist/cli/commands/analyze-skill.js +217 -0
- package/dist/cli/commands/analyze-skill.js.map +1 -0
- package/dist/cli/commands/analyze.d.ts +18 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +30 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/benchmark-runner.d.ts +42 -0
- package/dist/cli/commands/benchmark-runner.d.ts.map +1 -0
- package/dist/cli/commands/benchmark-runner.js +18 -0
- package/dist/cli/commands/benchmark-runner.js.map +1 -0
- package/dist/cli/commands/benchmark.d.ts +11 -0
- package/dist/cli/commands/benchmark.d.ts.map +1 -0
- package/dist/cli/commands/benchmark.js +90 -0
- package/dist/cli/commands/benchmark.js.map +1 -0
- package/dist/cli/commands/dead-code.d.ts +11 -0
- package/dist/cli/commands/dead-code.d.ts.map +1 -0
- package/dist/cli/commands/dead-code.js +65 -0
- package/dist/cli/commands/dead-code.js.map +1 -0
- package/dist/cli/commands/generate-spec.d.ts +11 -0
- package/dist/cli/commands/generate-spec.d.ts.map +1 -0
- package/dist/cli/commands/generate-spec.js +67 -0
- package/dist/cli/commands/generate-spec.js.map +1 -0
- package/dist/cli/commands/health.d.ts +11 -0
- package/dist/cli/commands/health.d.ts.map +1 -0
- package/dist/cli/commands/health.js +67 -0
- package/dist/cli/commands/health.js.map +1 -0
- package/dist/cli/commands/project.d.ts +21 -0
- package/dist/cli/commands/project.d.ts.map +1 -0
- package/dist/cli/commands/project.js +92 -0
- package/dist/cli/commands/project.js.map +1 -0
- package/dist/cli/commands/scan.d.ts +11 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/scan.js +68 -0
- package/dist/cli/commands/scan.js.map +1 -0
- package/dist/cli/commands/secrets.d.ts +11 -0
- package/dist/cli/commands/secrets.d.ts.map +1 -0
- package/dist/cli/commands/secrets.js +71 -0
- package/dist/cli/commands/secrets.js.map +1 -0
- package/dist/cli/commands/swarm.d.ts +20 -0
- package/dist/cli/commands/swarm.d.ts.map +1 -0
- package/dist/cli/commands/swarm.js +174 -0
- package/dist/cli/commands/swarm.js.map +1 -0
- package/dist/cli/config.d.ts +103 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +307 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/discovery.d.ts +31 -0
- package/dist/cli/discovery.d.ts.map +1 -0
- package/dist/cli/discovery.js +212 -0
- package/dist/cli/discovery.js.map +1 -0
- package/dist/cli/formatters/index.d.ts +15 -0
- package/dist/cli/formatters/index.d.ts.map +1 -0
- package/dist/cli/formatters/index.js +51 -0
- package/dist/cli/formatters/index.js.map +1 -0
- package/dist/cli/formatters/json.d.ts +11 -0
- package/dist/cli/formatters/json.d.ts.map +1 -0
- package/dist/cli/formatters/json.js +12 -0
- package/dist/cli/formatters/json.js.map +1 -0
- package/dist/cli/formatters/project-json.d.ts +11 -0
- package/dist/cli/formatters/project-json.d.ts.map +1 -0
- package/dist/cli/formatters/project-json.js +12 -0
- package/dist/cli/formatters/project-json.js.map +1 -0
- package/dist/cli/formatters/project-sarif.d.ts +11 -0
- package/dist/cli/formatters/project-sarif.d.ts.map +1 -0
- package/dist/cli/formatters/project-sarif.js +127 -0
- package/dist/cli/formatters/project-sarif.js.map +1 -0
- package/dist/cli/formatters/project-summary.d.ts +11 -0
- package/dist/cli/formatters/project-summary.d.ts.map +1 -0
- package/dist/cli/formatters/project-summary.js +202 -0
- package/dist/cli/formatters/project-summary.js.map +1 -0
- package/dist/cli/formatters/sarif-shared.d.ts +101 -0
- package/dist/cli/formatters/sarif-shared.d.ts.map +1 -0
- package/dist/cli/formatters/sarif-shared.js +57 -0
- package/dist/cli/formatters/sarif-shared.js.map +1 -0
- package/dist/cli/formatters/sarif.d.ts +12 -0
- package/dist/cli/formatters/sarif.d.ts.map +1 -0
- package/dist/cli/formatters/sarif.js +92 -0
- package/dist/cli/formatters/sarif.js.map +1 -0
- package/dist/cli/formatters/summary.d.ts +11 -0
- package/dist/cli/formatters/summary.d.ts.map +1 -0
- package/dist/cli/formatters/summary.js +240 -0
- package/dist/cli/formatters/summary.js.map +1 -0
- package/dist/cli/formatters/two-phase-summary.d.ts +11 -0
- package/dist/cli/formatters/two-phase-summary.d.ts.map +1 -0
- package/dist/cli/formatters/two-phase-summary.js +188 -0
- package/dist/cli/formatters/two-phase-summary.js.map +1 -0
- package/dist/cli/index.d.ts +15 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +555 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/components/clustering.d.ts +60 -0
- package/dist/components/clustering.d.ts.map +1 -0
- package/dist/components/clustering.js +129 -0
- package/dist/components/clustering.js.map +1 -0
- package/dist/components/enrichment.d.ts +45 -0
- package/dist/components/enrichment.d.ts.map +1 -0
- package/dist/components/enrichment.js +193 -0
- package/dist/components/enrichment.js.map +1 -0
- package/dist/components/index.d.ts +29 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +56 -0
- package/dist/components/index.js.map +1 -0
- package/dist/dead-code/detector.d.ts +200 -0
- package/dist/dead-code/detector.d.ts.map +1 -0
- package/dist/dead-code/detector.js +1003 -0
- package/dist/dead-code/detector.js.map +1 -0
- package/dist/dead-code/index.d.ts +7 -0
- package/dist/dead-code/index.d.ts.map +1 -0
- package/dist/dead-code/index.js +7 -0
- package/dist/dead-code/index.js.map +1 -0
- package/dist/extractors/index.d.ts +15 -0
- package/dist/extractors/index.d.ts.map +1 -0
- package/dist/extractors/index.js +14 -0
- package/dist/extractors/index.js.map +1 -0
- package/dist/extractors/natural-language.d.ts +46 -0
- package/dist/extractors/natural-language.d.ts.map +1 -0
- package/dist/extractors/natural-language.js +228 -0
- package/dist/extractors/natural-language.js.map +1 -0
- package/dist/extractors/tree-sitter.d.ts +33 -0
- package/dist/extractors/tree-sitter.d.ts.map +1 -0
- package/dist/extractors/tree-sitter.js +69 -0
- package/dist/extractors/tree-sitter.js.map +1 -0
- package/dist/extractors/types.d.ts +62 -0
- package/dist/extractors/types.d.ts.map +1 -0
- package/dist/extractors/types.js +54 -0
- package/dist/extractors/types.js.map +1 -0
- package/dist/health-score/calculator.d.ts +123 -0
- package/dist/health-score/calculator.d.ts.map +1 -0
- package/dist/health-score/calculator.js +444 -0
- package/dist/health-score/calculator.js.map +1 -0
- package/dist/health-score/index.d.ts +12 -0
- package/dist/health-score/index.d.ts.map +1 -0
- package/dist/health-score/index.js +14 -0
- package/dist/health-score/index.js.map +1 -0
- package/dist/health-score/metrics.d.ts +142 -0
- package/dist/health-score/metrics.d.ts.map +1 -0
- package/dist/health-score/metrics.js +332 -0
- package/dist/health-score/metrics.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/ax-client.d.ts +477 -0
- package/dist/llm/ax-client.d.ts.map +1 -0
- package/dist/llm/ax-client.js +1641 -0
- package/dist/llm/ax-client.js.map +1 -0
- package/dist/llm/config.d.ts +58 -0
- package/dist/llm/config.d.ts.map +1 -0
- package/dist/llm/config.js +97 -0
- package/dist/llm/config.js.map +1 -0
- package/dist/llm/discovery.d.ts +123 -0
- package/dist/llm/discovery.d.ts.map +1 -0
- package/dist/llm/discovery.js +505 -0
- package/dist/llm/discovery.js.map +1 -0
- package/dist/llm/enrichment.d.ts +108 -0
- package/dist/llm/enrichment.d.ts.map +1 -0
- package/dist/llm/enrichment.js +312 -0
- package/dist/llm/enrichment.js.map +1 -0
- package/dist/llm/index.d.ts +13 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +22 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/language-context.d.ts +64 -0
- package/dist/llm/language-context.d.ts.map +1 -0
- package/dist/llm/language-context.js +492 -0
- package/dist/llm/language-context.js.map +1 -0
- package/dist/llm/pattern-verification.d.ts +39 -0
- package/dist/llm/pattern-verification.d.ts.map +1 -0
- package/dist/llm/pattern-verification.js +127 -0
- package/dist/llm/pattern-verification.js.map +1 -0
- package/dist/llm/prompt-security.d.ts +120 -0
- package/dist/llm/prompt-security.d.ts.map +1 -0
- package/dist/llm/prompt-security.js +301 -0
- package/dist/llm/prompt-security.js.map +1 -0
- package/dist/llm/prompts/index.d.ts +31 -0
- package/dist/llm/prompts/index.d.ts.map +1 -0
- package/dist/llm/prompts/index.js +92 -0
- package/dist/llm/prompts/index.js.map +1 -0
- package/dist/llm/prompts/rust.d.ts +30 -0
- package/dist/llm/prompts/rust.d.ts.map +1 -0
- package/dist/llm/prompts/rust.js +121 -0
- package/dist/llm/prompts/rust.js.map +1 -0
- package/dist/llm/schemas.d.ts +892 -0
- package/dist/llm/schemas.d.ts.map +1 -0
- package/dist/llm/schemas.js +258 -0
- package/dist/llm/schemas.js.map +1 -0
- package/dist/llm/verification.d.ts +127 -0
- package/dist/llm/verification.d.ts.map +1 -0
- package/dist/llm/verification.js +394 -0
- package/dist/llm/verification.js.map +1 -0
- package/dist/project/analyzer.d.ts +30 -0
- package/dist/project/analyzer.d.ts.map +1 -0
- package/dist/project/analyzer.js +358 -0
- package/dist/project/analyzer.js.map +1 -0
- package/dist/project/call-graph.d.ts +22 -0
- package/dist/project/call-graph.d.ts.map +1 -0
- package/dist/project/call-graph.js +246 -0
- package/dist/project/call-graph.js.map +1 -0
- package/dist/project/index.d.ts +18 -0
- package/dist/project/index.d.ts.map +1 -0
- package/dist/project/index.js +20 -0
- package/dist/project/index.js.map +1 -0
- package/dist/project/taint-paths.d.ts +22 -0
- package/dist/project/taint-paths.d.ts.map +1 -0
- package/dist/project/taint-paths.js +265 -0
- package/dist/project/taint-paths.js.map +1 -0
- package/dist/project/two-phase-analyzer.d.ts +143 -0
- package/dist/project/two-phase-analyzer.d.ts.map +1 -0
- package/dist/project/two-phase-analyzer.js +646 -0
- package/dist/project/two-phase-analyzer.js.map +1 -0
- package/dist/project/type-hierarchy.d.ts +28 -0
- package/dist/project/type-hierarchy.d.ts.map +1 -0
- package/dist/project/type-hierarchy.js +218 -0
- package/dist/project/type-hierarchy.js.map +1 -0
- package/dist/secret-scan/index.d.ts +12 -0
- package/dist/secret-scan/index.d.ts.map +1 -0
- package/dist/secret-scan/index.js +14 -0
- package/dist/secret-scan/index.js.map +1 -0
- package/dist/secret-scan/patterns.d.ts +38 -0
- package/dist/secret-scan/patterns.d.ts.map +1 -0
- package/dist/secret-scan/patterns.js +473 -0
- package/dist/secret-scan/patterns.js.map +1 -0
- package/dist/secret-scan/scanner.d.ts +162 -0
- package/dist/secret-scan/scanner.d.ts.map +1 -0
- package/dist/secret-scan/scanner.js +511 -0
- package/dist/secret-scan/scanner.js.map +1 -0
- package/dist/security-scan/index.d.ts +12 -0
- package/dist/security-scan/index.d.ts.map +1 -0
- package/dist/security-scan/index.js +15 -0
- package/dist/security-scan/index.js.map +1 -0
- package/dist/security-scan/owasp-mapping.d.ts +29 -0
- package/dist/security-scan/owasp-mapping.d.ts.map +1 -0
- package/dist/security-scan/owasp-mapping.js +246 -0
- package/dist/security-scan/owasp-mapping.js.map +1 -0
- package/dist/security-scan/scanner.d.ts +204 -0
- package/dist/security-scan/scanner.d.ts.map +1 -0
- package/dist/security-scan/scanner.js +693 -0
- package/dist/security-scan/scanner.js.map +1 -0
- package/dist/security-scan/trend-tracker.d.ts +150 -0
- package/dist/security-scan/trend-tracker.d.ts.map +1 -0
- package/dist/security-scan/trend-tracker.js +299 -0
- package/dist/security-scan/trend-tracker.js.map +1 -0
- package/dist/skills/bundle-loader.d.ts +26 -0
- package/dist/skills/bundle-loader.d.ts.map +1 -0
- package/dist/skills/bundle-loader.js +284 -0
- package/dist/skills/bundle-loader.js.map +1 -0
- package/dist/skills/capability-mismatch.d.ts +21 -0
- package/dist/skills/capability-mismatch.d.ts.map +1 -0
- package/dist/skills/capability-mismatch.js +188 -0
- package/dist/skills/capability-mismatch.js.map +1 -0
- package/dist/skills/index.d.ts +10 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +9 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/skill-analyzer.d.ts +16 -0
- package/dist/skills/skill-analyzer.d.ts.map +1 -0
- package/dist/skills/skill-analyzer.js +361 -0
- package/dist/skills/skill-analyzer.js.map +1 -0
- package/dist/skills/types.d.ts +195 -0
- package/dist/skills/types.d.ts.map +1 -0
- package/dist/skills/types.js +7 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/specifica/conflict-resolver.d.ts +23 -0
- package/dist/specifica/conflict-resolver.d.ts.map +1 -0
- package/dist/specifica/conflict-resolver.js +129 -0
- package/dist/specifica/conflict-resolver.js.map +1 -0
- package/dist/specifica/evidence-aggregator.d.ts +33 -0
- package/dist/specifica/evidence-aggregator.d.ts.map +1 -0
- package/dist/specifica/evidence-aggregator.js +236 -0
- package/dist/specifica/evidence-aggregator.js.map +1 -0
- package/dist/specifica/evidence-extractor.d.ts +13 -0
- package/dist/specifica/evidence-extractor.d.ts.map +1 -0
- package/dist/specifica/evidence-extractor.js +431 -0
- package/dist/specifica/evidence-extractor.js.map +1 -0
- package/dist/specifica/feature-clustering.d.ts +19 -0
- package/dist/specifica/feature-clustering.d.ts.map +1 -0
- package/dist/specifica/feature-clustering.js +231 -0
- package/dist/specifica/feature-clustering.js.map +1 -0
- package/dist/specifica/generator.d.ts +16 -0
- package/dist/specifica/generator.d.ts.map +1 -0
- package/dist/specifica/generator.js +277 -0
- package/dist/specifica/generator.js.map +1 -0
- package/dist/specifica/index.d.ts +15 -0
- package/dist/specifica/index.d.ts.map +1 -0
- package/dist/specifica/index.js +18 -0
- package/dist/specifica/index.js.map +1 -0
- package/dist/specifica/prompts.d.ts +21 -0
- package/dist/specifica/prompts.d.ts.map +1 -0
- package/dist/specifica/prompts.js +196 -0
- package/dist/specifica/prompts.js.map +1 -0
- package/dist/specifica/spec-generator.d.ts +22 -0
- package/dist/specifica/spec-generator.d.ts.map +1 -0
- package/dist/specifica/spec-generator.js +229 -0
- package/dist/specifica/spec-generator.js.map +1 -0
- package/dist/specifica/types.d.ts +213 -0
- package/dist/specifica/types.d.ts.map +1 -0
- package/dist/specifica/types.js +7 -0
- package/dist/specifica/types.js.map +1 -0
- package/dist/utils/logger.d.ts +17 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +51 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +99 -0
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two-Phase Project Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Phase 1: Per-file analysis (parallelizable)
|
|
5
|
+
* - Static analysis (circle-ir)
|
|
6
|
+
* - LLM enrichment (sources, sinks, roles)
|
|
7
|
+
* - CFG/DFG extraction
|
|
8
|
+
*
|
|
9
|
+
* Phase 2: Cross-file LLM analysis
|
|
10
|
+
* - Sees ALL enriched IRs
|
|
11
|
+
* - Cross-file taint path detection
|
|
12
|
+
* - Virtual dispatch resolution across files
|
|
13
|
+
* - Final vulnerability verification
|
|
14
|
+
*/
|
|
15
|
+
import { analyze, initAnalyzer, isAnalyzerInitialized, createWithJdkTypes, SymbolTable, CrossFileResolver, } from 'circle-ir';
|
|
16
|
+
import { buildTypeHierarchy } from './type-hierarchy.js';
|
|
17
|
+
import { buildCrossFileCalls } from './call-graph.js';
|
|
18
|
+
import { findTaintPaths } from './taint-paths.js';
|
|
19
|
+
import { runSecurityAnalysis } from '../agents/security-agent.js';
|
|
20
|
+
import { getAxLLMClient } from '../llm/ax-client.js';
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Phase 1: Per-File Analysis
|
|
23
|
+
// ============================================================================
|
|
24
|
+
async function analyzeFilePhase1(file, options) {
|
|
25
|
+
const startTime = Date.now();
|
|
26
|
+
// Detect language
|
|
27
|
+
const language = detectLanguage(file.path);
|
|
28
|
+
// Static analysis with circle-ir
|
|
29
|
+
const staticAnalysis = await analyze(file.content, file.path, language);
|
|
30
|
+
// Prepare result
|
|
31
|
+
const result = {
|
|
32
|
+
file: file.path,
|
|
33
|
+
staticAnalysis,
|
|
34
|
+
allSources: [...staticAnalysis.taint.sources],
|
|
35
|
+
allSinks: [...staticAnalysis.taint.sinks],
|
|
36
|
+
intraFileFlows: [],
|
|
37
|
+
analysisTimeMs: 0,
|
|
38
|
+
};
|
|
39
|
+
// LLM enrichment (if enabled)
|
|
40
|
+
if (options.enableEnrichment !== false) {
|
|
41
|
+
const enrichmentStart = Date.now();
|
|
42
|
+
// Reset circuit breaker per file so one file's LLM failures
|
|
43
|
+
// don't disable LLM for the rest of the project
|
|
44
|
+
try {
|
|
45
|
+
getAxLLMClient().resetCircuitBreaker();
|
|
46
|
+
}
|
|
47
|
+
catch { /* client not initialized */ }
|
|
48
|
+
try {
|
|
49
|
+
const securityOutput = await runSecurityAnalysis({
|
|
50
|
+
filePath: file.path,
|
|
51
|
+
sourceCode: file.content,
|
|
52
|
+
language,
|
|
53
|
+
options: {
|
|
54
|
+
enableEnrichment: true,
|
|
55
|
+
enableVerification: false, // Phase 2 handles verification
|
|
56
|
+
confidenceThreshold: options.enrichmentConfidence ?? 0.7,
|
|
57
|
+
},
|
|
58
|
+
}, staticAnalysis.taint.sources, staticAnalysis.taint.sinks, staticAnalysis.types, staticAnalysis.imports.map(i => i.from_package || ''));
|
|
59
|
+
// Extract enrichment results
|
|
60
|
+
result.enrichment = {
|
|
61
|
+
role: securityOutput.context.enrichmentResult?.role,
|
|
62
|
+
additionalSources: securityOutput.context.enrichmentResult?.additionalSources ?? [],
|
|
63
|
+
additionalSinks: securityOutput.context.enrichmentResult?.additionalSinks ?? [],
|
|
64
|
+
framework: securityOutput.context.enrichmentResult?.framework,
|
|
65
|
+
};
|
|
66
|
+
// Merge enriched sources/sinks
|
|
67
|
+
const minConfidence = options.enrichmentConfidence ?? 0.7;
|
|
68
|
+
for (const src of result.enrichment.additionalSources) {
|
|
69
|
+
if (src.confidence >= minConfidence) {
|
|
70
|
+
result.allSources.push({
|
|
71
|
+
line: src.line,
|
|
72
|
+
location: `${src.variable} in ${file.path}`,
|
|
73
|
+
type: src.type,
|
|
74
|
+
severity: 'medium', // Default severity for LLM-discovered sources
|
|
75
|
+
confidence: src.confidence,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const sink of result.enrichment.additionalSinks) {
|
|
80
|
+
if (sink.confidence >= minConfidence) {
|
|
81
|
+
result.allSinks.push({
|
|
82
|
+
line: sink.line,
|
|
83
|
+
location: `${sink.method} in ${file.path}`,
|
|
84
|
+
type: sink.type,
|
|
85
|
+
cwe: sink.cwe,
|
|
86
|
+
confidence: sink.confidence,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
result.enrichmentTimeMs = Date.now() - enrichmentStart;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.warn(`[Phase1] Enrichment failed for ${file.path}:`, error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Extract intra-file flows from static analysis
|
|
97
|
+
if (staticAnalysis.taint.flows) {
|
|
98
|
+
for (const flow of staticAnalysis.taint.flows) {
|
|
99
|
+
const sourceIndex = result.allSources.findIndex(s => s.line === flow.source_line);
|
|
100
|
+
const sinkIndex = result.allSinks.findIndex(s => s.line === flow.sink_line);
|
|
101
|
+
if (sourceIndex >= 0 && sinkIndex >= 0) {
|
|
102
|
+
result.intraFileFlows.push({
|
|
103
|
+
sourceIndex,
|
|
104
|
+
sinkIndex,
|
|
105
|
+
confidence: flow.confidence,
|
|
106
|
+
sanitized: flow.sanitized,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
result.analysisTimeMs = Date.now() - startTime;
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Run Phase 1 on all files (with optional parallelization)
|
|
116
|
+
*/
|
|
117
|
+
async function runPhase1(files, options) {
|
|
118
|
+
const results = [];
|
|
119
|
+
if (options.parallelPhase1 !== false) {
|
|
120
|
+
// Parallel execution
|
|
121
|
+
const maxConcurrency = options.maxConcurrency ?? 10;
|
|
122
|
+
const queue = [...files];
|
|
123
|
+
let index = 0;
|
|
124
|
+
const workers = [];
|
|
125
|
+
for (let i = 0; i < Math.min(maxConcurrency, files.length); i++) {
|
|
126
|
+
workers.push((async () => {
|
|
127
|
+
while (queue.length > 0) {
|
|
128
|
+
const file = queue.shift();
|
|
129
|
+
if (!file)
|
|
130
|
+
break;
|
|
131
|
+
const currentIndex = index++;
|
|
132
|
+
options.onFileStart?.(file.path, currentIndex, files.length);
|
|
133
|
+
const result = await analyzeFilePhase1(file, options);
|
|
134
|
+
results[currentIndex] = result;
|
|
135
|
+
options.onFileComplete?.(file.path, currentIndex, files.length);
|
|
136
|
+
}
|
|
137
|
+
})());
|
|
138
|
+
}
|
|
139
|
+
await Promise.all(workers);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
// Sequential execution
|
|
143
|
+
for (let i = 0; i < files.length; i++) {
|
|
144
|
+
const file = files[i];
|
|
145
|
+
options.onFileStart?.(file.path, i, files.length);
|
|
146
|
+
const result = await analyzeFilePhase1(file, options);
|
|
147
|
+
results.push(result);
|
|
148
|
+
options.onFileComplete?.(file.path, i, files.length);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return results;
|
|
152
|
+
}
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// Aggregation: Build Project Context
|
|
155
|
+
// ============================================================================
|
|
156
|
+
function buildProjectContext(files, typeHierarchy, crossFileCalls) {
|
|
157
|
+
// Collect all sources and sinks with file info
|
|
158
|
+
const allSources = [];
|
|
159
|
+
const allSinks = [];
|
|
160
|
+
let totalLoc = 0;
|
|
161
|
+
for (const fileAnalysis of files) {
|
|
162
|
+
totalLoc += fileAnalysis.staticAnalysis.meta.loc;
|
|
163
|
+
for (const source of fileAnalysis.allSources) {
|
|
164
|
+
allSources.push({ ...source, file: fileAnalysis.file });
|
|
165
|
+
}
|
|
166
|
+
for (const sink of fileAnalysis.allSinks) {
|
|
167
|
+
allSinks.push({ ...sink, file: fileAnalysis.file });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Find candidate cross-file paths
|
|
171
|
+
const candidatePaths = [];
|
|
172
|
+
// Look for sources in one file that could reach sinks in another file
|
|
173
|
+
// via the cross-file call graph
|
|
174
|
+
for (const source of allSources) {
|
|
175
|
+
for (const sink of allSinks) {
|
|
176
|
+
if (source.file === sink.file)
|
|
177
|
+
continue; // Intra-file handled separately
|
|
178
|
+
// Check if there's a call path from source file to sink file
|
|
179
|
+
const pathExists = crossFileCalls.some(call => call.from.file === source.file && call.to.file === sink.file);
|
|
180
|
+
if (pathExists) {
|
|
181
|
+
candidatePaths.push({
|
|
182
|
+
id: `candidate-${source.file}:${source.line}-${sink.file}:${sink.line}`,
|
|
183
|
+
source: {
|
|
184
|
+
file: source.file,
|
|
185
|
+
line: source.line,
|
|
186
|
+
type: source.type,
|
|
187
|
+
code: source.location,
|
|
188
|
+
},
|
|
189
|
+
sink: {
|
|
190
|
+
file: sink.file,
|
|
191
|
+
line: sink.line,
|
|
192
|
+
type: sink.type,
|
|
193
|
+
cwe: sink.cwe || 'CWE-unknown',
|
|
194
|
+
code: sink.location,
|
|
195
|
+
},
|
|
196
|
+
hops: [],
|
|
197
|
+
path_exists: true,
|
|
198
|
+
sanitizers_in_path: [],
|
|
199
|
+
confidence: 0.5, // Will be updated by Phase 2
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Detect framework from enrichment results
|
|
205
|
+
let framework;
|
|
206
|
+
for (const fileAnalysis of files) {
|
|
207
|
+
const confidence = fileAnalysis.enrichment?.framework?.confidence;
|
|
208
|
+
if (confidence !== undefined && confidence > 0.8) {
|
|
209
|
+
framework = fileAnalysis.enrichment?.framework?.name;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
files,
|
|
215
|
+
typeHierarchy,
|
|
216
|
+
crossFileCalls,
|
|
217
|
+
allSources,
|
|
218
|
+
allSinks,
|
|
219
|
+
candidatePaths,
|
|
220
|
+
meta: {
|
|
221
|
+
totalFiles: files.length,
|
|
222
|
+
totalLoc,
|
|
223
|
+
totalSources: allSources.length,
|
|
224
|
+
totalSinks: allSinks.length,
|
|
225
|
+
framework,
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// ============================================================================
|
|
230
|
+
// Phase 2: Cross-File LLM Analysis
|
|
231
|
+
// ============================================================================
|
|
232
|
+
/**
|
|
233
|
+
* Extract keywords from a symbol description for fuzzy matching.
|
|
234
|
+
* E.g., "request.getParameter(\"id\") in getUser" -> ["getParameter", "getUser", "id"]
|
|
235
|
+
*/
|
|
236
|
+
function extractKeywords(symbol) {
|
|
237
|
+
const keywords = [];
|
|
238
|
+
// Extract method names (e.g., getParameter, findById)
|
|
239
|
+
const methodMatches = symbol.match(/\b[a-z][a-zA-Z0-9]*\(/g);
|
|
240
|
+
if (methodMatches) {
|
|
241
|
+
keywords.push(...methodMatches.map(m => m.slice(0, -1))); // Remove trailing (
|
|
242
|
+
}
|
|
243
|
+
// Extract identifiers after "in" (e.g., "in getUser" -> "getUser")
|
|
244
|
+
const inMatch = symbol.match(/\bin\s+(\w+)/);
|
|
245
|
+
if (inMatch) {
|
|
246
|
+
keywords.push(inMatch[1]);
|
|
247
|
+
}
|
|
248
|
+
// Extract identifiers that look like variable/param names
|
|
249
|
+
const identifiers = symbol.match(/\b[a-z][a-zA-Z0-9]*\b/g);
|
|
250
|
+
if (identifiers) {
|
|
251
|
+
// Filter out common words
|
|
252
|
+
const commonWords = ['in', 'the', 'to', 'from', 'with', 'for', 'and', 'or', 'of'];
|
|
253
|
+
keywords.push(...identifiers.filter(id => !commonWords.includes(id) && id.length > 2));
|
|
254
|
+
}
|
|
255
|
+
// Remove duplicates and return
|
|
256
|
+
return [...new Set(keywords)];
|
|
257
|
+
}
|
|
258
|
+
async function runPhase2(context, files, options) {
|
|
259
|
+
if (options.enablePhase2 === false) {
|
|
260
|
+
return [];
|
|
261
|
+
}
|
|
262
|
+
if (context.candidatePaths.length === 0) {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
const crossFileFlows = [];
|
|
266
|
+
try {
|
|
267
|
+
const client = getAxLLMClient();
|
|
268
|
+
// Group candidate paths by source-target file pairs
|
|
269
|
+
const filePairs = new Map();
|
|
270
|
+
for (const path of context.candidatePaths) {
|
|
271
|
+
const key = `${path.source.file}::${path.sink.file}`;
|
|
272
|
+
if (!filePairs.has(key)) {
|
|
273
|
+
filePairs.set(key, []);
|
|
274
|
+
}
|
|
275
|
+
filePairs.get(key).push(path);
|
|
276
|
+
}
|
|
277
|
+
// Call LLM for each file pair
|
|
278
|
+
for (const [pairKey, paths] of filePairs) {
|
|
279
|
+
const [sourceFilePath, targetFilePath] = pairKey.split('::');
|
|
280
|
+
const sourceFile = files.find(f => f.path === sourceFilePath);
|
|
281
|
+
const targetFile = files.find(f => f.path === targetFilePath);
|
|
282
|
+
if (!sourceFile || !targetFile)
|
|
283
|
+
continue;
|
|
284
|
+
// Extract exported taint from source file
|
|
285
|
+
const sourceAnalysis = context.files.find(f => f.file === sourceFilePath);
|
|
286
|
+
const exportedTaint = sourceAnalysis?.allSources.map(s => ({
|
|
287
|
+
symbol: s.location,
|
|
288
|
+
type: s.type,
|
|
289
|
+
line: s.line,
|
|
290
|
+
})) || [];
|
|
291
|
+
// Extract imported symbols in target file
|
|
292
|
+
const targetAnalysis = context.files.find(f => f.file === targetFilePath);
|
|
293
|
+
const importedSymbols = targetAnalysis?.staticAnalysis.imports
|
|
294
|
+
.map(i => i.imported_name) || [];
|
|
295
|
+
// Build code context around candidate source/sink locations for this file pair
|
|
296
|
+
const sourceLines = sourceFile.content.split('\n');
|
|
297
|
+
const targetLines = targetFile.content.split('\n');
|
|
298
|
+
const candidateContext = paths.slice(0, 10).map(p => {
|
|
299
|
+
const srcCtx = sourceLines.slice(Math.max(0, p.source.line - 5), p.source.line + 5).join('\n');
|
|
300
|
+
const sinkCtx = targetLines.slice(Math.max(0, p.sink.line - 5), p.sink.line + 5).join('\n');
|
|
301
|
+
return `Path: ${p.source.type}@${p.source.file}:${p.source.line} -> ${p.sink.type}@${p.sink.file}:${p.sink.line}\nSource context:\n${srcCtx}\nSink context:\n${sinkCtx}`;
|
|
302
|
+
}).join('\n---\n');
|
|
303
|
+
try {
|
|
304
|
+
const response = await client.analyzeCrossFileTaint({
|
|
305
|
+
sourceFile: sourceFilePath,
|
|
306
|
+
sourceCode: sourceFile.content,
|
|
307
|
+
targetFile: targetFilePath,
|
|
308
|
+
targetCode: targetFile.content,
|
|
309
|
+
exportedTaint,
|
|
310
|
+
importedSymbols,
|
|
311
|
+
candidateContext,
|
|
312
|
+
});
|
|
313
|
+
// Process LLM response into CrossFileTaintFlow objects
|
|
314
|
+
for (const flow of response.taintFlows) {
|
|
315
|
+
// Find matching candidate paths with fuzzy matching
|
|
316
|
+
// Extract key parts from symbols (method names, variable names)
|
|
317
|
+
const sourceKeywords = extractKeywords(flow.sourceSymbol);
|
|
318
|
+
const targetKeywords = extractKeywords(flow.targetSymbol);
|
|
319
|
+
const candidatePath = paths.find(p => {
|
|
320
|
+
const sourceMatches = sourceKeywords.some(kw => p.source.code.toLowerCase().includes(kw.toLowerCase()));
|
|
321
|
+
const sinkMatches = targetKeywords.some(kw => p.sink.code.toLowerCase().includes(kw.toLowerCase()));
|
|
322
|
+
// Match if source keywords match source OR target keywords match sink
|
|
323
|
+
return sourceMatches || sinkMatches;
|
|
324
|
+
});
|
|
325
|
+
if (candidatePath) {
|
|
326
|
+
crossFileFlows.push({
|
|
327
|
+
id: candidatePath.id,
|
|
328
|
+
source: {
|
|
329
|
+
file: candidatePath.source.file,
|
|
330
|
+
line: candidatePath.source.line,
|
|
331
|
+
type: candidatePath.source.type,
|
|
332
|
+
code: candidatePath.source.code,
|
|
333
|
+
},
|
|
334
|
+
sink: {
|
|
335
|
+
file: candidatePath.sink.file,
|
|
336
|
+
line: candidatePath.sink.line,
|
|
337
|
+
type: candidatePath.sink.type,
|
|
338
|
+
cwe: candidatePath.sink.cwe,
|
|
339
|
+
code: candidatePath.sink.code,
|
|
340
|
+
},
|
|
341
|
+
path: [{
|
|
342
|
+
file: targetFilePath,
|
|
343
|
+
line: 0,
|
|
344
|
+
variable: flow.targetSymbol,
|
|
345
|
+
operation: flow.flowType,
|
|
346
|
+
}],
|
|
347
|
+
verified: flow.confidence > (options.crossFileConfidence ?? 0.8),
|
|
348
|
+
confidence: flow.confidence,
|
|
349
|
+
exploitability: flow.confidence > 0.8 ? 'high' : flow.confidence > 0.5 ? 'medium' : 'low',
|
|
350
|
+
reasoning: response.reasoning || 'Cross-file taint flow detected',
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch (pairError) {
|
|
356
|
+
console.warn(`[Phase2] Analysis failed for ${pairKey}:`, pairError);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
console.warn('[Phase2] Cross-file analysis failed:', error);
|
|
362
|
+
}
|
|
363
|
+
return crossFileFlows;
|
|
364
|
+
}
|
|
365
|
+
// ============================================================================
|
|
366
|
+
// Main Entry Point
|
|
367
|
+
// ============================================================================
|
|
368
|
+
/**
|
|
369
|
+
* Analyze a project using two-phase LLM-enhanced analysis.
|
|
370
|
+
*
|
|
371
|
+
* Phase 1: Per-file static analysis + LLM enrichment (parallelizable)
|
|
372
|
+
* Phase 2: Cross-file LLM analysis with full project context
|
|
373
|
+
*/
|
|
374
|
+
export async function analyzeProjectTwoPhase(files, options = {}) {
|
|
375
|
+
const totalStartTime = Date.now();
|
|
376
|
+
// Ensure analyzer is initialized
|
|
377
|
+
if (!isAnalyzerInitialized()) {
|
|
378
|
+
await initAnalyzer();
|
|
379
|
+
}
|
|
380
|
+
// =========================================================================
|
|
381
|
+
// PHASE 1: Per-File Analysis
|
|
382
|
+
// =========================================================================
|
|
383
|
+
const phase1Start = Date.now();
|
|
384
|
+
const enrichedFiles = await runPhase1(files, options);
|
|
385
|
+
const phase1Time = Date.now() - phase1Start;
|
|
386
|
+
options.onPhase1Complete?.(enrichedFiles);
|
|
387
|
+
// Convert to FileAnalysis for compatibility
|
|
388
|
+
const fileAnalyses = enrichedFiles.map(ef => ({
|
|
389
|
+
file: ef.file,
|
|
390
|
+
analysis: ef.staticAnalysis,
|
|
391
|
+
}));
|
|
392
|
+
// =========================================================================
|
|
393
|
+
// AGGREGATION: Build Project Context
|
|
394
|
+
// =========================================================================
|
|
395
|
+
// Initialize cross-file resolution infrastructure
|
|
396
|
+
const typeResolver = createWithJdkTypes();
|
|
397
|
+
const symbolTable = new SymbolTable();
|
|
398
|
+
const crossFileResolver = new CrossFileResolver(symbolTable, typeResolver);
|
|
399
|
+
// Register files with resolver
|
|
400
|
+
for (const ef of enrichedFiles) {
|
|
401
|
+
crossFileResolver.addFile(ef.file, ef.staticAnalysis);
|
|
402
|
+
}
|
|
403
|
+
// Build type hierarchy
|
|
404
|
+
const typeHierarchy = buildTypeHierarchy(fileAnalyses);
|
|
405
|
+
// Merge type hierarchy from resolver
|
|
406
|
+
for (const type of typeResolver.getAllTypes()) {
|
|
407
|
+
if (type.kind === 'class' || type.kind === 'enum') {
|
|
408
|
+
if (!typeHierarchy.classes[type.fqn]) {
|
|
409
|
+
typeHierarchy.classes[type.fqn] = {
|
|
410
|
+
file: type.file,
|
|
411
|
+
extends: type.extends || null,
|
|
412
|
+
implements: type.implements,
|
|
413
|
+
subclasses: typeResolver.getDirectSubtypes(type.fqn),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
else if (type.kind === 'interface') {
|
|
418
|
+
if (!typeHierarchy.interfaces[type.fqn]) {
|
|
419
|
+
typeHierarchy.interfaces[type.fqn] = {
|
|
420
|
+
file: type.file,
|
|
421
|
+
extends: type.extendsInterfaces,
|
|
422
|
+
implementations: typeResolver.getDirectImplementations(type.fqn),
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// Build cross-file call graph
|
|
428
|
+
const crossFileCalls = buildCrossFileCalls(fileAnalyses, typeHierarchy);
|
|
429
|
+
// Build project context for Phase 2
|
|
430
|
+
const projectContext = buildProjectContext(enrichedFiles, typeHierarchy, crossFileCalls);
|
|
431
|
+
// =========================================================================
|
|
432
|
+
// PHASE 2: Cross-File LLM Analysis
|
|
433
|
+
// =========================================================================
|
|
434
|
+
options.onPhase2Start?.();
|
|
435
|
+
const phase2Start = Date.now();
|
|
436
|
+
const crossFileFlows = await runPhase2(projectContext, files, options);
|
|
437
|
+
const phase2Time = Date.now() - phase2Start;
|
|
438
|
+
// =========================================================================
|
|
439
|
+
// Generate Final Results
|
|
440
|
+
// =========================================================================
|
|
441
|
+
// Find intra-file taint paths
|
|
442
|
+
let taintPaths = findTaintPaths(fileAnalyses, crossFileCalls);
|
|
443
|
+
// Add verified cross-file flows to taint paths
|
|
444
|
+
for (const flow of crossFileFlows) {
|
|
445
|
+
if (flow.verified) {
|
|
446
|
+
taintPaths.push({
|
|
447
|
+
id: flow.id,
|
|
448
|
+
source: {
|
|
449
|
+
file: flow.source.file,
|
|
450
|
+
line: flow.source.line,
|
|
451
|
+
type: flow.source.type,
|
|
452
|
+
code: flow.source.code,
|
|
453
|
+
},
|
|
454
|
+
sink: {
|
|
455
|
+
file: flow.sink.file,
|
|
456
|
+
line: flow.sink.line,
|
|
457
|
+
type: flow.sink.type,
|
|
458
|
+
cwe: flow.sink.cwe,
|
|
459
|
+
code: flow.sink.code,
|
|
460
|
+
},
|
|
461
|
+
hops: flow.path.map(p => ({
|
|
462
|
+
file: p.file,
|
|
463
|
+
method: 'cross-file',
|
|
464
|
+
line: p.line,
|
|
465
|
+
code: p.operation,
|
|
466
|
+
variable: p.variable,
|
|
467
|
+
})),
|
|
468
|
+
path_exists: true,
|
|
469
|
+
sanitizers_in_path: [],
|
|
470
|
+
confidence: flow.confidence,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// Aggregate findings
|
|
475
|
+
const findings = aggregateFindings(fileAnalyses, taintPaths, crossFileFlows);
|
|
476
|
+
// Build project metadata
|
|
477
|
+
const meta = {
|
|
478
|
+
name: options.name ?? extractProjectName(files),
|
|
479
|
+
root: options.root ?? extractRoot(files),
|
|
480
|
+
language: options.language ?? detectPrimaryLanguage(files),
|
|
481
|
+
framework: options.framework ?? projectContext.meta.framework,
|
|
482
|
+
framework_version: options.frameworkVersion,
|
|
483
|
+
total_files: files.length,
|
|
484
|
+
total_loc: projectContext.meta.totalLoc,
|
|
485
|
+
analyzed_at: new Date().toISOString(),
|
|
486
|
+
};
|
|
487
|
+
const totalTime = Date.now() - totalStartTime;
|
|
488
|
+
return {
|
|
489
|
+
meta,
|
|
490
|
+
files: fileAnalyses,
|
|
491
|
+
type_hierarchy: typeHierarchy,
|
|
492
|
+
cross_file_calls: crossFileCalls,
|
|
493
|
+
taint_paths: taintPaths,
|
|
494
|
+
findings,
|
|
495
|
+
// Two-phase specific additions
|
|
496
|
+
enrichedFiles,
|
|
497
|
+
crossFileFlows,
|
|
498
|
+
timing: {
|
|
499
|
+
phase1TotalMs: phase1Time,
|
|
500
|
+
phase2TotalMs: phase2Time,
|
|
501
|
+
totalMs: totalTime,
|
|
502
|
+
},
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
// ============================================================================
|
|
506
|
+
// Helper Functions
|
|
507
|
+
// ============================================================================
|
|
508
|
+
function detectLanguage(filePath) {
|
|
509
|
+
if (filePath.endsWith('.java'))
|
|
510
|
+
return 'java';
|
|
511
|
+
if (filePath.endsWith('.c') || filePath.endsWith('.h'))
|
|
512
|
+
return 'c';
|
|
513
|
+
if (filePath.endsWith('.cpp') || filePath.endsWith('.cc') ||
|
|
514
|
+
filePath.endsWith('.cxx') || filePath.endsWith('.hpp'))
|
|
515
|
+
return 'cpp';
|
|
516
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.jsx') ||
|
|
517
|
+
filePath.endsWith('.mjs') || filePath.endsWith('.cjs'))
|
|
518
|
+
return 'javascript';
|
|
519
|
+
if (filePath.endsWith('.ts') || filePath.endsWith('.tsx') ||
|
|
520
|
+
filePath.endsWith('.mts') || filePath.endsWith('.cts'))
|
|
521
|
+
return 'typescript';
|
|
522
|
+
if (filePath.endsWith('.py'))
|
|
523
|
+
return 'python';
|
|
524
|
+
return 'java';
|
|
525
|
+
}
|
|
526
|
+
function detectPrimaryLanguage(files) {
|
|
527
|
+
const counts = { java: 0, c: 0, cpp: 0, javascript: 0, typescript: 0, python: 0, rust: 0 };
|
|
528
|
+
for (const file of files) {
|
|
529
|
+
counts[detectLanguage(file.path)]++;
|
|
530
|
+
}
|
|
531
|
+
return Object.entries(counts).sort((a, b) => b[1] - a[1])[0][0];
|
|
532
|
+
}
|
|
533
|
+
function extractProjectName(files) {
|
|
534
|
+
if (files.length === 0)
|
|
535
|
+
return 'unknown';
|
|
536
|
+
const paths = files.map(f => f.path.split('/'));
|
|
537
|
+
let commonPrefix = [];
|
|
538
|
+
for (let i = 0; i < paths[0].length; i++) {
|
|
539
|
+
if (paths.every(p => p[i] === paths[0][i])) {
|
|
540
|
+
commonPrefix.push(paths[0][i]);
|
|
541
|
+
}
|
|
542
|
+
else
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
return commonPrefix.length > 0 ? commonPrefix[commonPrefix.length - 1] : 'unknown';
|
|
546
|
+
}
|
|
547
|
+
function extractRoot(files) {
|
|
548
|
+
if (files.length === 0)
|
|
549
|
+
return '/';
|
|
550
|
+
const paths = files.map(f => f.path.split('/'));
|
|
551
|
+
const commonPrefix = [];
|
|
552
|
+
for (let i = 0; i < paths[0].length - 1; i++) {
|
|
553
|
+
if (paths.every(p => p[i] === paths[0][i])) {
|
|
554
|
+
commonPrefix.push(paths[0][i]);
|
|
555
|
+
}
|
|
556
|
+
else
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
return commonPrefix.join('/') || '/';
|
|
560
|
+
}
|
|
561
|
+
function calculateSeverity(input) {
|
|
562
|
+
const criticalSinks = ['sql_injection', 'command_injection', 'code_injection', 'deserialization'];
|
|
563
|
+
const highSinks = ['xss', 'path_traversal', 'xxe', 'ssrf', 'ldap_injection'];
|
|
564
|
+
if (criticalSinks.some(s => input.sinkType.includes(s))) {
|
|
565
|
+
return input.pathExists ? 'critical' : 'high';
|
|
566
|
+
}
|
|
567
|
+
if (highSinks.some(s => input.sinkType.includes(s))) {
|
|
568
|
+
return input.pathExists ? 'high' : 'medium';
|
|
569
|
+
}
|
|
570
|
+
return input.pathExists ? 'medium' : 'low';
|
|
571
|
+
}
|
|
572
|
+
function getRemediation(sinkType) {
|
|
573
|
+
const remediations = {
|
|
574
|
+
sql_injection: 'Use parameterized queries or prepared statements',
|
|
575
|
+
command_injection: 'Avoid shell execution; use array-based APIs with validated input',
|
|
576
|
+
xss: 'Encode output using context-appropriate encoding (HTML, JavaScript, URL)',
|
|
577
|
+
path_traversal: 'Validate and canonicalize paths; use allowlists',
|
|
578
|
+
xxe: 'Disable external entity processing in XML parsers',
|
|
579
|
+
ssrf: 'Validate URLs against allowlist; block internal network access',
|
|
580
|
+
deserialization: 'Avoid deserializing untrusted data; use allowlists',
|
|
581
|
+
ldap_injection: 'Use parameterized LDAP queries; escape special characters',
|
|
582
|
+
};
|
|
583
|
+
for (const [key, value] of Object.entries(remediations)) {
|
|
584
|
+
if (sinkType.includes(key))
|
|
585
|
+
return value;
|
|
586
|
+
}
|
|
587
|
+
return 'Validate and sanitize all user input before use';
|
|
588
|
+
}
|
|
589
|
+
function aggregateFindings(fileAnalyses, taintPaths, crossFileFlows) {
|
|
590
|
+
const findings = [];
|
|
591
|
+
const seenIds = new Set();
|
|
592
|
+
// Generate findings from taint paths
|
|
593
|
+
for (const path of taintPaths) {
|
|
594
|
+
if (path.path_exists && path.sanitizers_in_path.length === 0) {
|
|
595
|
+
const id = `${path.source.file}:${path.source.line}-${path.sink.file}:${path.sink.line}`;
|
|
596
|
+
if (!seenIds.has(id)) {
|
|
597
|
+
seenIds.add(id);
|
|
598
|
+
const crossFileFlow = crossFileFlows.find(f => f.id === path.id);
|
|
599
|
+
findings.push({
|
|
600
|
+
id,
|
|
601
|
+
type: path.sink.type,
|
|
602
|
+
cwe: path.sink.cwe,
|
|
603
|
+
severity: calculateSeverity({
|
|
604
|
+
sourceType: path.source.type,
|
|
605
|
+
sinkType: path.sink.type,
|
|
606
|
+
pathExists: path.path_exists,
|
|
607
|
+
confidence: path.confidence,
|
|
608
|
+
}),
|
|
609
|
+
confidence: path.confidence,
|
|
610
|
+
source: {
|
|
611
|
+
file: path.source.file,
|
|
612
|
+
line: path.source.line,
|
|
613
|
+
code: path.source.code,
|
|
614
|
+
},
|
|
615
|
+
sink: {
|
|
616
|
+
file: path.sink.file,
|
|
617
|
+
line: path.sink.line,
|
|
618
|
+
code: path.sink.code,
|
|
619
|
+
},
|
|
620
|
+
path: path.hops,
|
|
621
|
+
exploitable: crossFileFlow?.exploitability === 'high' || (path.path_exists && path.confidence > 0.7),
|
|
622
|
+
explanation: generateExplanation(path),
|
|
623
|
+
remediation: getRemediation(path.sink.type),
|
|
624
|
+
verification: {
|
|
625
|
+
graph_path_exists: path.path_exists,
|
|
626
|
+
llm_verified: crossFileFlow?.verified ?? false,
|
|
627
|
+
llm_confidence: crossFileFlow?.confidence ?? 0,
|
|
628
|
+
},
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// Sort by severity
|
|
634
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
635
|
+
findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
636
|
+
return findings;
|
|
637
|
+
}
|
|
638
|
+
function generateExplanation(path) {
|
|
639
|
+
const sourceDesc = `User-controlled data from ${path.source.type} at ${path.source.file}:${path.source.line}`;
|
|
640
|
+
const sinkDesc = `reaches a ${path.sink.type} sink at ${path.sink.file}:${path.sink.line}`;
|
|
641
|
+
const hopDesc = path.hops.length > 0
|
|
642
|
+
? ` through ${path.hops.length} intermediate step(s)`
|
|
643
|
+
: ' directly';
|
|
644
|
+
return `${sourceDesc}${hopDesc} and ${sinkDesc}. ${path.sink.cwe}`;
|
|
645
|
+
}
|
|
646
|
+
//# sourceMappingURL=two-phase-analyzer.js.map
|