mcp-adr-analysis-server 2.0.21 → 2.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/README.md +94 -839
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/config/ai-config.d.ts.map +1 -1
- package/dist/src/config/ai-config.js +9 -9
- package/dist/src/config/ai-config.js.map +1 -1
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1594 -837
- package/dist/src/index.js.map +1 -1
- package/dist/src/prompts/analysis-prompts.d.ts +4 -1
- package/dist/src/prompts/analysis-prompts.d.ts.map +1 -1
- package/dist/src/prompts/analysis-prompts.js +61 -30
- package/dist/src/prompts/analysis-prompts.js.map +1 -1
- package/dist/src/prompts/index.d.ts +17 -1
- package/dist/src/prompts/index.d.ts.map +1 -1
- package/dist/src/prompts/index.js +325 -1
- package/dist/src/prompts/index.js.map +1 -1
- package/dist/src/prompts/rule-generation-prompts.d.ts +5 -1
- package/dist/src/prompts/rule-generation-prompts.d.ts.map +1 -1
- package/dist/src/prompts/rule-generation-prompts.js +5 -1
- package/dist/src/prompts/rule-generation-prompts.js.map +1 -1
- package/dist/src/prompts/security-prompts.d.ts +6 -1
- package/dist/src/prompts/security-prompts.d.ts.map +1 -1
- package/dist/src/prompts/security-prompts.js +6 -1
- package/dist/src/prompts/security-prompts.js.map +1 -1
- package/dist/src/prompts/todo-prompts.d.ts +49 -0
- package/dist/src/prompts/todo-prompts.d.ts.map +1 -0
- package/dist/src/prompts/todo-prompts.js +824 -0
- package/dist/src/prompts/todo-prompts.js.map +1 -0
- package/dist/src/resources/index.d.ts.map +1 -1
- package/dist/src/resources/index.js +9 -7
- package/dist/src/resources/index.js.map +1 -1
- package/dist/src/tools/adr-bootstrap-validation-tool.d.ts +47 -0
- package/dist/src/tools/adr-bootstrap-validation-tool.d.ts.map +1 -0
- package/dist/src/tools/adr-bootstrap-validation-tool.js +979 -0
- package/dist/src/tools/adr-bootstrap-validation-tool.js.map +1 -0
- package/dist/src/tools/adr-suggestion-tool.d.ts +1 -0
- package/dist/src/tools/adr-suggestion-tool.d.ts.map +1 -1
- package/dist/src/tools/adr-suggestion-tool.js +330 -32
- package/dist/src/tools/adr-suggestion-tool.js.map +1 -1
- package/dist/src/tools/content-masking-tool.d.ts +5 -0
- package/dist/src/tools/content-masking-tool.d.ts.map +1 -1
- package/dist/src/tools/content-masking-tool.js +631 -28
- package/dist/src/tools/content-masking-tool.js.map +1 -1
- package/dist/src/tools/deployment-readiness-tool.d.ts +8 -5
- package/dist/src/tools/deployment-readiness-tool.d.ts.map +1 -1
- package/dist/src/tools/deployment-readiness-tool.js +1017 -93
- package/dist/src/tools/deployment-readiness-tool.js.map +1 -1
- package/dist/src/tools/environment-analysis-tool.d.ts +4 -0
- package/dist/src/tools/environment-analysis-tool.d.ts.map +1 -1
- package/dist/src/tools/environment-analysis-tool.js +568 -24
- package/dist/src/tools/environment-analysis-tool.js.map +1 -1
- package/dist/src/tools/interactive-adr-planning-tool.d.ts.map +1 -1
- package/dist/src/tools/interactive-adr-planning-tool.js +270 -108
- package/dist/src/tools/interactive-adr-planning-tool.js.map +1 -1
- package/dist/src/tools/mcp-planning-tool.d.ts.map +1 -1
- package/dist/src/tools/mcp-planning-tool.js +316 -183
- package/dist/src/tools/mcp-planning-tool.js.map +1 -1
- package/dist/src/tools/memory-loading-tool.d.ts +60 -0
- package/dist/src/tools/memory-loading-tool.d.ts.map +1 -0
- package/dist/src/tools/memory-loading-tool.js +457 -0
- package/dist/src/tools/memory-loading-tool.js.map +1 -0
- package/dist/src/tools/research-integration-tool.d.ts +4 -1
- package/dist/src/tools/research-integration-tool.d.ts.map +1 -1
- package/dist/src/tools/research-integration-tool.js +5 -1
- package/dist/src/tools/research-integration-tool.js.map +1 -1
- package/dist/src/tools/review-existing-adrs-tool.d.ts +19 -0
- package/dist/src/tools/review-existing-adrs-tool.d.ts.map +1 -0
- package/dist/src/tools/review-existing-adrs-tool.js +1204 -0
- package/dist/src/tools/review-existing-adrs-tool.js.map +1 -0
- package/dist/src/tools/rule-generation-tool.d.ts +2 -0
- package/dist/src/tools/rule-generation-tool.d.ts.map +1 -1
- package/dist/src/tools/rule-generation-tool.js +274 -33
- package/dist/src/tools/rule-generation-tool.js.map +1 -1
- package/dist/src/tools/smart-git-push-tool-v2.d.ts +4 -0
- package/dist/src/tools/smart-git-push-tool-v2.d.ts.map +1 -1
- package/dist/src/tools/smart-git-push-tool-v2.js +423 -229
- package/dist/src/tools/smart-git-push-tool-v2.js.map +1 -1
- package/dist/src/tools/smart-git-push-tool.d.ts +3 -0
- package/dist/src/tools/smart-git-push-tool.d.ts.map +1 -1
- package/dist/src/tools/smart-git-push-tool.js +241 -235
- package/dist/src/tools/smart-git-push-tool.js.map +1 -1
- package/dist/src/tools/tool-chain-orchestrator.d.ts +2 -2
- package/dist/src/tools/tool-chain-orchestrator.d.ts.map +1 -1
- package/dist/src/tools/tool-chain-orchestrator.js +152 -93
- package/dist/src/tools/tool-chain-orchestrator.js.map +1 -1
- package/dist/src/tools/troubleshoot-guided-workflow-tool.d.ts +22 -13
- package/dist/src/tools/troubleshoot-guided-workflow-tool.d.ts.map +1 -1
- package/dist/src/tools/troubleshoot-guided-workflow-tool.js +660 -28
- package/dist/src/tools/troubleshoot-guided-workflow-tool.js.map +1 -1
- package/dist/src/types/ape-framework.d.ts +93 -0
- package/dist/src/types/ape-framework.d.ts.map +1 -1
- package/dist/src/types/ape-framework.js +10 -10
- package/dist/src/types/ape-framework.js.map +1 -1
- package/dist/src/types/enhanced-errors.d.ts +173 -0
- package/dist/src/types/enhanced-errors.d.ts.map +1 -0
- package/dist/src/types/enhanced-errors.js +668 -0
- package/dist/src/types/enhanced-errors.js.map +1 -0
- package/dist/src/types/index.d.ts +129 -3
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +15 -0
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/knowledge-generation.d.ts +78 -0
- package/dist/src/types/knowledge-generation.d.ts.map +1 -1
- package/dist/src/types/knowledge-generation.js +10 -6
- package/dist/src/types/knowledge-generation.js.map +1 -1
- package/dist/src/types/knowledge-graph-schemas.d.ts +92 -0
- package/dist/src/types/knowledge-graph-schemas.d.ts.map +1 -1
- package/dist/src/types/knowledge-graph-schemas.js +45 -17
- package/dist/src/types/knowledge-graph-schemas.js.map +1 -1
- package/dist/src/types/memory-entities.d.ts +7209 -0
- package/dist/src/types/memory-entities.d.ts.map +1 -0
- package/dist/src/types/memory-entities.js +395 -0
- package/dist/src/types/memory-entities.js.map +1 -0
- package/dist/src/types/reflexion-framework.d.ts +44 -0
- package/dist/src/types/reflexion-framework.d.ts.map +1 -1
- package/dist/src/types/reflexion-framework.js +7 -7
- package/dist/src/types/reflexion-framework.js.map +1 -1
- package/dist/src/types/todo-json-schemas.d.ts +7 -653
- package/dist/src/types/todo-json-schemas.d.ts.map +1 -1
- package/dist/src/types/todo-json-schemas.js +6 -110
- package/dist/src/types/todo-json-schemas.js.map +1 -1
- package/dist/src/types/tool-arguments.d.ts +224 -0
- package/dist/src/types/tool-arguments.d.ts.map +1 -0
- package/dist/src/types/tool-arguments.js +6 -0
- package/dist/src/types/tool-arguments.js.map +1 -0
- package/dist/src/utils/actual-file-operations.d.ts.map +1 -1
- package/dist/src/utils/actual-file-operations.js +25 -9
- package/dist/src/utils/actual-file-operations.js.map +1 -1
- package/dist/src/utils/adr-discovery.d.ts +33 -1
- package/dist/src/utils/adr-discovery.d.ts.map +1 -1
- package/dist/src/utils/adr-discovery.js +14 -9
- package/dist/src/utils/adr-discovery.js.map +1 -1
- package/dist/src/utils/adr-suggestions.d.ts +72 -0
- package/dist/src/utils/adr-suggestions.d.ts.map +1 -1
- package/dist/src/utils/adr-suggestions.js +6 -0
- package/dist/src/utils/adr-suggestions.js.map +1 -1
- package/dist/src/utils/cache.d.ts.map +1 -1
- package/dist/src/utils/cache.js +22 -19
- package/dist/src/utils/cache.js.map +1 -1
- package/dist/src/utils/chain-of-thought-template.d.ts +13 -1
- package/dist/src/utils/chain-of-thought-template.d.ts.map +1 -1
- package/dist/src/utils/chain-of-thought-template.js +31 -22
- package/dist/src/utils/chain-of-thought-template.js.map +1 -1
- package/dist/src/utils/config.d.ts.map +1 -1
- package/dist/src/utils/config.js +1 -0
- package/dist/src/utils/config.js.map +1 -1
- package/dist/src/utils/directory-compat.js +1 -1
- package/dist/src/utils/directory-compat.js.map +1 -1
- package/dist/src/utils/enhanced-logging.d.ts +195 -0
- package/dist/src/utils/enhanced-logging.d.ts.map +1 -0
- package/dist/src/utils/enhanced-logging.js +449 -0
- package/dist/src/utils/enhanced-logging.js.map +1 -0
- package/dist/src/utils/file-system.d.ts +182 -34
- package/dist/src/utils/file-system.d.ts.map +1 -1
- package/dist/src/utils/file-system.js +648 -774
- package/dist/src/utils/file-system.js.map +1 -1
- package/dist/src/utils/gitleaks-detector.d.ts +50 -0
- package/dist/src/utils/gitleaks-detector.d.ts.map +1 -0
- package/dist/src/utils/gitleaks-detector.js +334 -0
- package/dist/src/utils/gitleaks-detector.js.map +1 -0
- package/dist/src/utils/interactive-approval.d.ts +1 -1
- package/dist/src/utils/interactive-approval.d.ts.map +1 -1
- package/dist/src/utils/interactive-approval.js +22 -17
- package/dist/src/utils/interactive-approval.js.map +1 -1
- package/dist/src/utils/json-safe.d.ts.map +1 -1
- package/dist/src/utils/json-safe.js +9 -6
- package/dist/src/utils/json-safe.js.map +1 -1
- package/dist/src/utils/knowledge-graph-manager.d.ts +37 -1
- package/dist/src/utils/knowledge-graph-manager.d.ts.map +1 -1
- package/dist/src/utils/knowledge-graph-manager.js +315 -70
- package/dist/src/utils/knowledge-graph-manager.js.map +1 -1
- package/dist/src/utils/location-filter.d.ts +35 -10
- package/dist/src/utils/location-filter.d.ts.map +1 -1
- package/dist/src/utils/location-filter.js +360 -94
- package/dist/src/utils/location-filter.js.map +1 -1
- package/dist/src/utils/mcp-response-validator.d.ts.map +1 -1
- package/dist/src/utils/mcp-response-validator.js +34 -19
- package/dist/src/utils/mcp-response-validator.js.map +1 -1
- package/dist/src/utils/memory-entity-manager.d.ts +134 -0
- package/dist/src/utils/memory-entity-manager.d.ts.map +1 -0
- package/dist/src/utils/memory-entity-manager.js +851 -0
- package/dist/src/utils/memory-entity-manager.js.map +1 -0
- package/dist/src/utils/memory-health-scoring.d.ts +125 -0
- package/dist/src/utils/memory-health-scoring.d.ts.map +1 -0
- package/dist/src/utils/memory-health-scoring.js +277 -0
- package/dist/src/utils/memory-health-scoring.js.map +1 -0
- package/dist/src/utils/memory-migration-manager.d.ts +93 -0
- package/dist/src/utils/memory-migration-manager.d.ts.map +1 -0
- package/dist/src/utils/memory-migration-manager.js +842 -0
- package/dist/src/utils/memory-migration-manager.js.map +1 -0
- package/dist/src/utils/memory-relationship-mapper.d.ts +87 -0
- package/dist/src/utils/memory-relationship-mapper.d.ts.map +1 -0
- package/dist/src/utils/memory-relationship-mapper.js +482 -0
- package/dist/src/utils/memory-relationship-mapper.js.map +1 -0
- package/dist/src/utils/memory-rollback-manager.d.ts +105 -0
- package/dist/src/utils/memory-rollback-manager.d.ts.map +1 -0
- package/dist/src/utils/memory-rollback-manager.js +575 -0
- package/dist/src/utils/memory-rollback-manager.js.map +1 -0
- package/dist/src/utils/memory-transformation.d.ts +59 -0
- package/dist/src/utils/memory-transformation.d.ts.map +1 -0
- package/dist/src/utils/memory-transformation.js +742 -0
- package/dist/src/utils/memory-transformation.js.map +1 -0
- package/dist/src/utils/output-masking.d.ts +12 -0
- package/dist/src/utils/output-masking.d.ts.map +1 -1
- package/dist/src/utils/output-masking.js +5 -0
- package/dist/src/utils/output-masking.js.map +1 -1
- package/dist/src/utils/prompt-execution.d.ts.map +1 -1
- package/dist/src/utils/prompt-execution.js +72 -51
- package/dist/src/utils/prompt-execution.js.map +1 -1
- package/dist/src/utils/release-readiness-detector.d.ts.map +1 -1
- package/dist/src/utils/release-readiness-detector.js +43 -42
- package/dist/src/utils/release-readiness-detector.js.map +1 -1
- package/dist/src/utils/research-integration.d.ts.map +1 -1
- package/dist/src/utils/research-integration.js +73 -44
- package/dist/src/utils/research-integration.js.map +1 -1
- package/dist/src/utils/research-questions.d.ts.map +1 -1
- package/dist/src/utils/research-questions.js +40 -14
- package/dist/src/utils/research-questions.js.map +1 -1
- package/dist/src/utils/ripgrep-wrapper.d.ts +84 -0
- package/dist/src/utils/ripgrep-wrapper.d.ts.map +1 -0
- package/dist/src/utils/ripgrep-wrapper.js +255 -0
- package/dist/src/utils/ripgrep-wrapper.js.map +1 -0
- package/dist/src/utils/rule-generation.d.ts.map +1 -1
- package/dist/src/utils/rule-generation.js +26 -15
- package/dist/src/utils/rule-generation.js.map +1 -1
- package/dist/src/utils/todo-file-watcher.d.ts +11 -23
- package/dist/src/utils/todo-file-watcher.d.ts.map +1 -1
- package/dist/src/utils/todo-file-watcher.js +18 -156
- package/dist/src/utils/todo-file-watcher.js.map +1 -1
- package/dist/src/utils/tree-sitter-analyzer.d.ts +185 -0
- package/dist/src/utils/tree-sitter-analyzer.d.ts.map +1 -0
- package/dist/src/utils/tree-sitter-analyzer.js +885 -0
- package/dist/src/utils/tree-sitter-analyzer.js.map +1 -0
- package/package.json +51 -10
- package/dist/src/tools/smart-score-tool.d.ts +0 -173
- package/dist/src/tools/smart-score-tool.d.ts.map +0 -1
- package/dist/src/tools/smart-score-tool.js +0 -681
- package/dist/src/tools/smart-score-tool.js.map +0 -1
- package/dist/src/tools/todo-management-tool-v2.d.ts +0 -368
- package/dist/src/tools/todo-management-tool-v2.d.ts.map +0 -1
- package/dist/src/tools/todo-management-tool-v2.js +0 -922
- package/dist/src/tools/todo-management-tool-v2.js.map +0 -1
- package/dist/src/utils/enhanced-sensitive-detector.d.ts +0 -61
- package/dist/src/utils/enhanced-sensitive-detector.d.ts.map +0 -1
- package/dist/src/utils/enhanced-sensitive-detector.js +0 -437
- package/dist/src/utils/enhanced-sensitive-detector.js.map +0 -1
- package/dist/src/utils/project-health-scoring.d.ts +0 -177
- package/dist/src/utils/project-health-scoring.d.ts.map +0 -1
- package/dist/src/utils/project-health-scoring.js +0 -370
- package/dist/src/utils/project-health-scoring.js.map +0 -1
- package/dist/src/utils/todo-json-manager.d.ts +0 -71
- package/dist/src/utils/todo-json-manager.d.ts.map +0 -1
- package/dist/src/utils/todo-json-manager.js +0 -396
- package/dist/src/utils/todo-json-manager.js.map +0 -1
- package/dist/src/utils/todo-markdown-converter.d.ts +0 -16
- package/dist/src/utils/todo-markdown-converter.d.ts.map +0 -1
- package/dist/src/utils/todo-markdown-converter.js +0 -362
- package/dist/src/utils/todo-markdown-converter.js.map +0 -1
- package/dist/src/utils/todo-migration.d.ts +0 -47
- package/dist/src/utils/todo-migration.d.ts.map +0 -1
- package/dist/src/utils/todo-migration.js +0 -261
- package/dist/src/utils/todo-migration.js.map +0 -1
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-sitter Integration for Enterprise DevOps Code Analysis
|
|
3
|
+
*
|
|
4
|
+
* Provides intelligent code analysis for multi-language DevOps stacks including:
|
|
5
|
+
* - Ansible playbooks and roles
|
|
6
|
+
* - Terraform/HCL infrastructure
|
|
7
|
+
* - Python microservices
|
|
8
|
+
* - Node.js applications
|
|
9
|
+
* - Container configurations
|
|
10
|
+
* - CI/CD pipelines
|
|
11
|
+
*
|
|
12
|
+
* Enterprise Features:
|
|
13
|
+
* - Secret detection in code
|
|
14
|
+
* - Architectural boundary validation
|
|
15
|
+
* - Multi-language dependency analysis
|
|
16
|
+
* - Security pattern recognition
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync } from 'fs';
|
|
19
|
+
import { extname, basename } from 'path';
|
|
20
|
+
/**
|
|
21
|
+
* Main Tree-sitter analyzer class for enterprise DevOps stacks
|
|
22
|
+
*/
|
|
23
|
+
export class TreeSitterAnalyzer {
|
|
24
|
+
parsers = new Map();
|
|
25
|
+
initialized = false;
|
|
26
|
+
constructor() {
|
|
27
|
+
this.initializeParsers();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Initialize tree-sitter parsers with graceful fallback
|
|
31
|
+
*/
|
|
32
|
+
async initializeParsers() {
|
|
33
|
+
try {
|
|
34
|
+
// Initialize core parsers for enterprise stack
|
|
35
|
+
await this.loadParser('typescript', 'tree-sitter-typescript');
|
|
36
|
+
await this.loadParser('javascript', 'tree-sitter-javascript');
|
|
37
|
+
await this.loadParser('python', 'tree-sitter-python');
|
|
38
|
+
await this.loadParser('yaml', 'tree-sitter-yaml');
|
|
39
|
+
await this.loadParser('json', 'tree-sitter-json');
|
|
40
|
+
await this.loadParser('bash', 'tree-sitter-bash');
|
|
41
|
+
await this.loadParser('dockerfile', 'tree-sitter-dockerfile');
|
|
42
|
+
await this.loadParser('hcl', '@tree-sitter-grammars/tree-sitter-hcl');
|
|
43
|
+
this.initialized = true;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.warn('Tree-sitter initialization failed, falling back to regex analysis:', error);
|
|
47
|
+
this.initialized = false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async loadParser(language, packageName) {
|
|
51
|
+
try {
|
|
52
|
+
const TreeSitterModule = await import('tree-sitter');
|
|
53
|
+
const TreeSitter = TreeSitterModule.default || TreeSitterModule;
|
|
54
|
+
let Parser;
|
|
55
|
+
if (language === 'typescript') {
|
|
56
|
+
const tsModule = await import('tree-sitter-typescript');
|
|
57
|
+
Parser = tsModule.typescript;
|
|
58
|
+
}
|
|
59
|
+
else if (language === 'hcl') {
|
|
60
|
+
const hclModule = await import('@tree-sitter-grammars/tree-sitter-hcl');
|
|
61
|
+
Parser = hclModule.default || hclModule;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const module = await import(packageName);
|
|
65
|
+
Parser = module.default || module;
|
|
66
|
+
}
|
|
67
|
+
const parser = new TreeSitter();
|
|
68
|
+
parser.setLanguage(Parser);
|
|
69
|
+
this.parsers.set(language, parser);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.warn(`Failed to load ${language} parser:`, error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Analyze code file with tree-sitter intelligence
|
|
77
|
+
*/
|
|
78
|
+
async analyzeFile(filePath, content) {
|
|
79
|
+
const fileContent = content || readFileSync(filePath, 'utf-8');
|
|
80
|
+
const language = this.detectLanguage(filePath);
|
|
81
|
+
if (!this.initialized || !this.parsers.has(language)) {
|
|
82
|
+
return this.fallbackAnalysis(filePath, fileContent, language);
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const parser = this.parsers.get(language);
|
|
86
|
+
const tree = parser.parse(fileContent);
|
|
87
|
+
return await this.performIntelligentAnalysis(tree, fileContent, language, filePath);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.warn(`Tree-sitter analysis failed for ${filePath}, falling back to regex:`, error);
|
|
91
|
+
return this.fallbackAnalysis(filePath, fileContent, language);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Detect programming language from file extension and content
|
|
96
|
+
*/
|
|
97
|
+
detectLanguage(filePath) {
|
|
98
|
+
const ext = extname(filePath).toLowerCase();
|
|
99
|
+
const filename = basename(filePath).toLowerCase();
|
|
100
|
+
// DevOps file detection
|
|
101
|
+
if (filename === 'dockerfile' || filename.startsWith('dockerfile.'))
|
|
102
|
+
return 'dockerfile';
|
|
103
|
+
if (filename.endsWith('.tf') || filename.endsWith('.tfvars'))
|
|
104
|
+
return 'hcl';
|
|
105
|
+
if (filename.includes('docker-compose') && (ext === '.yml' || ext === '.yaml'))
|
|
106
|
+
return 'yaml';
|
|
107
|
+
if (filename.includes('ansible') || filename.includes('playbook'))
|
|
108
|
+
return 'yaml';
|
|
109
|
+
if (filename.includes('k8s') || filename.includes('kubernetes'))
|
|
110
|
+
return 'yaml';
|
|
111
|
+
// Standard language detection
|
|
112
|
+
switch (ext) {
|
|
113
|
+
case '.ts':
|
|
114
|
+
case '.tsx':
|
|
115
|
+
return 'typescript';
|
|
116
|
+
case '.js':
|
|
117
|
+
case '.jsx':
|
|
118
|
+
case '.mjs':
|
|
119
|
+
return 'javascript';
|
|
120
|
+
case '.py':
|
|
121
|
+
case '.pyi':
|
|
122
|
+
return 'python';
|
|
123
|
+
case '.yml':
|
|
124
|
+
case '.yaml':
|
|
125
|
+
return 'yaml';
|
|
126
|
+
case '.json':
|
|
127
|
+
return 'json';
|
|
128
|
+
case '.sh':
|
|
129
|
+
case '.bash':
|
|
130
|
+
return 'bash';
|
|
131
|
+
case '.tf':
|
|
132
|
+
case '.tfvars':
|
|
133
|
+
return 'hcl';
|
|
134
|
+
default:
|
|
135
|
+
return 'text';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Perform intelligent analysis using tree-sitter AST
|
|
140
|
+
*/
|
|
141
|
+
async performIntelligentAnalysis(tree, content, language, filePath) {
|
|
142
|
+
const lines = content.split('\n');
|
|
143
|
+
const result = {
|
|
144
|
+
language,
|
|
145
|
+
hasSecrets: false,
|
|
146
|
+
secrets: [],
|
|
147
|
+
imports: [],
|
|
148
|
+
functions: [],
|
|
149
|
+
variables: [],
|
|
150
|
+
infraStructure: [],
|
|
151
|
+
securityIssues: [],
|
|
152
|
+
architecturalViolations: [],
|
|
153
|
+
};
|
|
154
|
+
// Language-specific analysis
|
|
155
|
+
switch (language) {
|
|
156
|
+
case 'python':
|
|
157
|
+
await this.analyzePython(tree.rootNode, lines, result);
|
|
158
|
+
break;
|
|
159
|
+
case 'typescript':
|
|
160
|
+
case 'javascript':
|
|
161
|
+
await this.analyzeJavaScript(tree.rootNode, lines, result);
|
|
162
|
+
break;
|
|
163
|
+
case 'yaml':
|
|
164
|
+
await this.analyzeYAML(tree.rootNode, lines, result, filePath);
|
|
165
|
+
break;
|
|
166
|
+
case 'hcl':
|
|
167
|
+
await this.analyzeTerraform(tree.rootNode, lines, result);
|
|
168
|
+
break;
|
|
169
|
+
case 'dockerfile':
|
|
170
|
+
await this.analyzeDockerfile(tree.rootNode, lines, result);
|
|
171
|
+
break;
|
|
172
|
+
case 'bash':
|
|
173
|
+
await this.analyzeBash(tree.rootNode, lines, result);
|
|
174
|
+
break;
|
|
175
|
+
case 'json':
|
|
176
|
+
await this.analyzeJSON(tree.rootNode, lines, result);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
// Universal security analysis
|
|
180
|
+
await this.analyzeSecrets(tree.rootNode, lines, result);
|
|
181
|
+
result.hasSecrets = result.secrets.length > 0;
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Python-specific analysis for microservices
|
|
186
|
+
*/
|
|
187
|
+
async analyzePython(node, _lines, result) {
|
|
188
|
+
// Find imports
|
|
189
|
+
const imports = node
|
|
190
|
+
.descendantsOfType('import_statement')
|
|
191
|
+
.concat(node.descendantsOfType('import_from_statement'));
|
|
192
|
+
for (const importNode of imports) {
|
|
193
|
+
const importText = importNode.text;
|
|
194
|
+
const location = {
|
|
195
|
+
line: importNode.startPosition.row + 1,
|
|
196
|
+
column: importNode.startPosition.column,
|
|
197
|
+
};
|
|
198
|
+
// Check for dangerous imports
|
|
199
|
+
const isDangerous = this.checkDangerousImport(importText, 'python');
|
|
200
|
+
result.imports.push({
|
|
201
|
+
module: importText,
|
|
202
|
+
type: 'import',
|
|
203
|
+
location,
|
|
204
|
+
isExternal: !importText.includes('.'),
|
|
205
|
+
isDangerous,
|
|
206
|
+
...(isDangerous && { reason: 'Potentially dangerous module' }),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
// Find functions
|
|
210
|
+
const functions = node.descendantsOfType('function_definition');
|
|
211
|
+
for (const funcNode of functions) {
|
|
212
|
+
const nameNode = funcNode.childForFieldName('name');
|
|
213
|
+
if (nameNode) {
|
|
214
|
+
result.functions.push({
|
|
215
|
+
name: nameNode.text,
|
|
216
|
+
type: 'function',
|
|
217
|
+
parameters: this.extractPythonParameters(funcNode),
|
|
218
|
+
location: { line: funcNode.startPosition.row + 1, column: funcNode.startPosition.column },
|
|
219
|
+
complexity: this.calculateComplexity(funcNode),
|
|
220
|
+
securitySensitive: this.isSecuritySensitive(nameNode.text),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Find variable assignments with potential secrets
|
|
225
|
+
const assignments = node.descendantsOfType('assignment');
|
|
226
|
+
for (const assignment of assignments) {
|
|
227
|
+
const leftNode = assignment.children[0];
|
|
228
|
+
const rightNode = assignment.children[2];
|
|
229
|
+
if (leftNode && rightNode) {
|
|
230
|
+
const varName = leftNode.text;
|
|
231
|
+
const varValue = rightNode.text;
|
|
232
|
+
result.variables.push({
|
|
233
|
+
name: varName,
|
|
234
|
+
type: 'var',
|
|
235
|
+
value: varValue,
|
|
236
|
+
location: {
|
|
237
|
+
line: assignment.startPosition.row + 1,
|
|
238
|
+
column: assignment.startPosition.column,
|
|
239
|
+
},
|
|
240
|
+
isSensitive: this.isSensitiveVariable(varName, varValue),
|
|
241
|
+
scope: 'module',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* JavaScript/TypeScript analysis for Node.js applications
|
|
248
|
+
*/
|
|
249
|
+
async analyzeJavaScript(node, _lines, result) {
|
|
250
|
+
// Find imports and requires
|
|
251
|
+
const imports = node
|
|
252
|
+
.descendantsOfType('import_statement')
|
|
253
|
+
.concat(node.descendantsOfType('call_expression'));
|
|
254
|
+
for (const importNode of imports) {
|
|
255
|
+
if (importNode.type === 'call_expression') {
|
|
256
|
+
const funcName = importNode.children[0]?.text;
|
|
257
|
+
if (funcName === 'require' && importNode.children[1]) {
|
|
258
|
+
const moduleNode = importNode.children[1].children[1]; // Get string content
|
|
259
|
+
if (moduleNode) {
|
|
260
|
+
result.imports.push({
|
|
261
|
+
module: moduleNode.text.replace(/['"]/g, ''),
|
|
262
|
+
type: 'require',
|
|
263
|
+
location: {
|
|
264
|
+
line: importNode.startPosition.row + 1,
|
|
265
|
+
column: importNode.startPosition.column,
|
|
266
|
+
},
|
|
267
|
+
isExternal: !moduleNode.text.includes('./'),
|
|
268
|
+
isDangerous: this.checkDangerousImport(moduleNode.text, 'javascript'),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
// ES6 import
|
|
275
|
+
const moduleNode = importNode.childForFieldName('source');
|
|
276
|
+
if (moduleNode) {
|
|
277
|
+
result.imports.push({
|
|
278
|
+
module: moduleNode.text.replace(/['"]/g, ''),
|
|
279
|
+
type: 'import',
|
|
280
|
+
location: {
|
|
281
|
+
line: importNode.startPosition.row + 1,
|
|
282
|
+
column: importNode.startPosition.column,
|
|
283
|
+
},
|
|
284
|
+
isExternal: !moduleNode.text.includes('./'),
|
|
285
|
+
isDangerous: this.checkDangerousImport(moduleNode.text, 'javascript'),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Find functions and methods
|
|
291
|
+
const functions = node
|
|
292
|
+
.descendantsOfType('function_declaration')
|
|
293
|
+
.concat(node.descendantsOfType('method_definition'), node.descendantsOfType('arrow_function'));
|
|
294
|
+
for (const funcNode of functions) {
|
|
295
|
+
const nameNode = funcNode.childForFieldName('name');
|
|
296
|
+
const name = nameNode?.text || 'anonymous';
|
|
297
|
+
result.functions.push({
|
|
298
|
+
name,
|
|
299
|
+
type: funcNode.type === 'method_definition' ? 'method' : 'function',
|
|
300
|
+
parameters: this.extractJSParameters(funcNode),
|
|
301
|
+
location: { line: funcNode.startPosition.row + 1, column: funcNode.startPosition.column },
|
|
302
|
+
complexity: this.calculateComplexity(funcNode),
|
|
303
|
+
securitySensitive: this.isSecuritySensitive(name),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* YAML analysis for Ansible, Kubernetes, Docker Compose
|
|
309
|
+
*/
|
|
310
|
+
async analyzeYAML(node, _lines, result, filePath) {
|
|
311
|
+
// Detect YAML type based on content and filename
|
|
312
|
+
const content = _lines.join('\n');
|
|
313
|
+
const filename = basename(filePath).toLowerCase();
|
|
314
|
+
if (this.isAnsibleFile(filename, content)) {
|
|
315
|
+
await this.analyzeAnsibleYAML(node, _lines, result);
|
|
316
|
+
}
|
|
317
|
+
else if (this.isKubernetesFile(filename, content)) {
|
|
318
|
+
await this.analyzeKubernetesYAML(node, _lines, result);
|
|
319
|
+
}
|
|
320
|
+
else if (this.isDockerComposeFile(filename, content)) {
|
|
321
|
+
await this.analyzeDockerComposeYAML(node, _lines, result);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Terraform/HCL analysis for infrastructure
|
|
326
|
+
*/
|
|
327
|
+
async analyzeTerraform(node, _lines, result) {
|
|
328
|
+
// Find resource blocks
|
|
329
|
+
const resources = node.descendantsOfType('block');
|
|
330
|
+
for (const resource of resources) {
|
|
331
|
+
const labels = resource.children.filter(child => child.type === 'identifier');
|
|
332
|
+
if (labels.length >= 2) {
|
|
333
|
+
const resourceType = labels[0]?.text || 'unknown';
|
|
334
|
+
const resourceName = labels[1]?.text || 'unnamed';
|
|
335
|
+
// Determine provider
|
|
336
|
+
let provider = 'aws';
|
|
337
|
+
if (resourceType.startsWith('google_'))
|
|
338
|
+
provider = 'gcp';
|
|
339
|
+
else if (resourceType.startsWith('azurerm_'))
|
|
340
|
+
provider = 'azure';
|
|
341
|
+
else if (resourceType.startsWith('kubernetes_'))
|
|
342
|
+
provider = 'kubernetes';
|
|
343
|
+
const securityRisks = this.analyzeTerraformSecurity(resource);
|
|
344
|
+
result.infraStructure.push({
|
|
345
|
+
resourceType,
|
|
346
|
+
name: resourceName,
|
|
347
|
+
provider,
|
|
348
|
+
configuration: {},
|
|
349
|
+
securityRisks,
|
|
350
|
+
location: { line: resource.startPosition.row + 1, column: resource.startPosition.column },
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Dockerfile analysis for container security
|
|
357
|
+
*/
|
|
358
|
+
async analyzeDockerfile(_node, lines, result) {
|
|
359
|
+
// Analyze each instruction
|
|
360
|
+
for (const line of lines) {
|
|
361
|
+
const trimmed = line.trim();
|
|
362
|
+
if (trimmed.startsWith('USER ')) {
|
|
363
|
+
const user = trimmed.substring(5).trim();
|
|
364
|
+
if (user === 'root') {
|
|
365
|
+
result.securityIssues.push({
|
|
366
|
+
type: 'privilege_escalation',
|
|
367
|
+
severity: 'high',
|
|
368
|
+
message: 'Running as root user in container',
|
|
369
|
+
location: { line: lines.indexOf(line) + 1, column: 0 },
|
|
370
|
+
suggestion: 'Create and use a non-root user',
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Bash script analysis
|
|
378
|
+
*/
|
|
379
|
+
async analyzeBash(node, _lines, result) {
|
|
380
|
+
// Find variable assignments and command substitutions
|
|
381
|
+
const assignments = node.descendantsOfType('variable_assignment');
|
|
382
|
+
for (const assignment of assignments) {
|
|
383
|
+
const nameNode = assignment.childForFieldName('name');
|
|
384
|
+
const valueNode = assignment.childForFieldName('value');
|
|
385
|
+
if (nameNode && valueNode) {
|
|
386
|
+
result.variables.push({
|
|
387
|
+
name: nameNode.text,
|
|
388
|
+
type: 'var',
|
|
389
|
+
value: valueNode.text,
|
|
390
|
+
location: {
|
|
391
|
+
line: assignment.startPosition.row + 1,
|
|
392
|
+
column: assignment.startPosition.column,
|
|
393
|
+
},
|
|
394
|
+
isSensitive: this.isSensitiveVariable(nameNode.text, valueNode.text),
|
|
395
|
+
scope: 'script',
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* JSON analysis for configuration files
|
|
402
|
+
*/
|
|
403
|
+
async analyzeJSON(node, _lines, result) {
|
|
404
|
+
// Analyze JSON objects for secrets
|
|
405
|
+
const pairs = node.descendantsOfType('pair');
|
|
406
|
+
for (const pair of pairs) {
|
|
407
|
+
const keyNode = pair.children[0];
|
|
408
|
+
const valueNode = pair.children[2];
|
|
409
|
+
if (keyNode && valueNode && valueNode.type === 'string') {
|
|
410
|
+
const key = keyNode.text.replace(/['"]/g, '');
|
|
411
|
+
const value = valueNode.text.replace(/['"]/g, '');
|
|
412
|
+
if (this.isSensitiveVariable(key, value)) {
|
|
413
|
+
result.secrets.push({
|
|
414
|
+
type: this.classifySecret(key, value),
|
|
415
|
+
value: value,
|
|
416
|
+
location: { line: pair.startPosition.row + 1, column: pair.startPosition.column },
|
|
417
|
+
confidence: 0.8,
|
|
418
|
+
context: `JSON key: ${key}`,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Universal secret detection across all languages
|
|
426
|
+
*/
|
|
427
|
+
async analyzeSecrets(node, _lines, result) {
|
|
428
|
+
// Find all string literals and analyze them
|
|
429
|
+
const strings = node
|
|
430
|
+
.descendantsOfType('string')
|
|
431
|
+
.concat(node.descendantsOfType('string_literal'));
|
|
432
|
+
for (const stringNode of strings) {
|
|
433
|
+
const text = stringNode.text.replace(/['"]/g, '');
|
|
434
|
+
const secretType = this.detectSecretPattern(text);
|
|
435
|
+
if (secretType) {
|
|
436
|
+
result.secrets.push({
|
|
437
|
+
type: secretType,
|
|
438
|
+
value: text,
|
|
439
|
+
location: {
|
|
440
|
+
line: stringNode.startPosition.row + 1,
|
|
441
|
+
column: stringNode.startPosition.column,
|
|
442
|
+
},
|
|
443
|
+
confidence: 0.7,
|
|
444
|
+
context: 'String literal',
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Fallback analysis using regex when tree-sitter fails
|
|
451
|
+
*/
|
|
452
|
+
fallbackAnalysis(_filePath, content, language) {
|
|
453
|
+
const lines = content.split('\n');
|
|
454
|
+
const result = {
|
|
455
|
+
language,
|
|
456
|
+
hasSecrets: false,
|
|
457
|
+
secrets: [],
|
|
458
|
+
imports: [],
|
|
459
|
+
functions: [],
|
|
460
|
+
variables: [],
|
|
461
|
+
infraStructure: [],
|
|
462
|
+
securityIssues: [],
|
|
463
|
+
architecturalViolations: [],
|
|
464
|
+
};
|
|
465
|
+
// Basic regex-based secret detection
|
|
466
|
+
const secretPatterns = [
|
|
467
|
+
{ pattern: /(?:api[_-]?key|apikey)[:=]\s*['""]([^'""]+)['""]?/i, type: 'api_key' },
|
|
468
|
+
{ pattern: /(?:password|pwd|pass)[:=]\s*['""]([^'""]+)['""]?/i, type: 'password' },
|
|
469
|
+
{ pattern: /(?:token|auth)[:=]\s*['""]([^'""]+)['""]?/i, type: 'token' },
|
|
470
|
+
{
|
|
471
|
+
pattern: /(?:secret|private[_-]?key)[:=]\s*['""]([^'""]+)['""]?/i,
|
|
472
|
+
type: 'private_key',
|
|
473
|
+
},
|
|
474
|
+
];
|
|
475
|
+
lines.forEach((line, index) => {
|
|
476
|
+
secretPatterns.forEach(({ pattern, type }) => {
|
|
477
|
+
const match = line.match(pattern);
|
|
478
|
+
if (match && match[1] && match[1].length > 8) {
|
|
479
|
+
result.secrets.push({
|
|
480
|
+
type,
|
|
481
|
+
value: match[1],
|
|
482
|
+
location: { line: index + 1, column: match.index || 0 },
|
|
483
|
+
confidence: 0.6,
|
|
484
|
+
context: line.trim(),
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
result.hasSecrets = result.secrets.length > 0;
|
|
490
|
+
return result;
|
|
491
|
+
}
|
|
492
|
+
// Helper methods
|
|
493
|
+
checkDangerousImport(importText, language) {
|
|
494
|
+
const dangerousModules = {
|
|
495
|
+
python: ['eval', 'exec', 'subprocess', 'os.system'],
|
|
496
|
+
javascript: ['eval', 'child_process', 'vm'],
|
|
497
|
+
};
|
|
498
|
+
const dangerous = dangerousModules[language] || [];
|
|
499
|
+
return dangerous.some(module => importText.includes(module));
|
|
500
|
+
}
|
|
501
|
+
extractPythonParameters(funcNode) {
|
|
502
|
+
const paramsNode = funcNode.childForFieldName('parameters');
|
|
503
|
+
if (!paramsNode)
|
|
504
|
+
return [];
|
|
505
|
+
return paramsNode.namedChildren
|
|
506
|
+
.filter(child => child.type === 'identifier')
|
|
507
|
+
.map(child => child.text);
|
|
508
|
+
}
|
|
509
|
+
extractJSParameters(funcNode) {
|
|
510
|
+
const paramsNode = funcNode.childForFieldName('parameters');
|
|
511
|
+
if (!paramsNode)
|
|
512
|
+
return [];
|
|
513
|
+
return paramsNode.namedChildren
|
|
514
|
+
.filter(child => child.type === 'identifier')
|
|
515
|
+
.map(child => child.text);
|
|
516
|
+
}
|
|
517
|
+
calculateComplexity(node) {
|
|
518
|
+
// Simple cyclomatic complexity calculation
|
|
519
|
+
const complexityNodes = ['if_statement', 'while_statement', 'for_statement', 'try_statement'];
|
|
520
|
+
let complexity = 1;
|
|
521
|
+
const walk = (n) => {
|
|
522
|
+
if (complexityNodes.includes(n.type)) {
|
|
523
|
+
complexity++;
|
|
524
|
+
}
|
|
525
|
+
n.children.forEach(child => walk(child));
|
|
526
|
+
};
|
|
527
|
+
walk(node);
|
|
528
|
+
return complexity;
|
|
529
|
+
}
|
|
530
|
+
isSecuritySensitive(name) {
|
|
531
|
+
const sensitiveNames = [
|
|
532
|
+
'auth',
|
|
533
|
+
'login',
|
|
534
|
+
'password',
|
|
535
|
+
'token',
|
|
536
|
+
'key',
|
|
537
|
+
'secret',
|
|
538
|
+
'decrypt',
|
|
539
|
+
'hash',
|
|
540
|
+
];
|
|
541
|
+
return sensitiveNames.some(sensitive => name.toLowerCase().includes(sensitive));
|
|
542
|
+
}
|
|
543
|
+
isSensitiveVariable(name, value) {
|
|
544
|
+
const sensitiveKeys = ['password', 'secret', 'key', 'token', 'auth', 'private', 'credential'];
|
|
545
|
+
const nameCheck = sensitiveKeys.some(key => name.toLowerCase().includes(key));
|
|
546
|
+
const valueCheck = Boolean(value && value.length > 16 && /^[A-Za-z0-9+/=]+$/.test(value));
|
|
547
|
+
return nameCheck || valueCheck;
|
|
548
|
+
}
|
|
549
|
+
classifySecret(key, _value) {
|
|
550
|
+
if (key.toLowerCase().includes('password'))
|
|
551
|
+
return 'password';
|
|
552
|
+
if (key.toLowerCase().includes('token'))
|
|
553
|
+
return 'token';
|
|
554
|
+
if (key.toLowerCase().includes('key'))
|
|
555
|
+
return 'api_key';
|
|
556
|
+
if (key.toLowerCase().includes('private'))
|
|
557
|
+
return 'private_key';
|
|
558
|
+
return 'credential';
|
|
559
|
+
}
|
|
560
|
+
detectSecretPattern(text) {
|
|
561
|
+
// AWS Access Key
|
|
562
|
+
if (/^AKIA[0-9A-Z]{16}$/.test(text))
|
|
563
|
+
return 'api_key';
|
|
564
|
+
// Generic API key patterns
|
|
565
|
+
if (/^[A-Za-z0-9]{32,}$/.test(text) && text.length >= 32)
|
|
566
|
+
return 'api_key';
|
|
567
|
+
// JWT Token
|
|
568
|
+
if (/^eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_.+/]*$/.test(text))
|
|
569
|
+
return 'token';
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
isAnsibleFile(filename, content) {
|
|
573
|
+
return (filename.includes('playbook') ||
|
|
574
|
+
filename.includes('ansible') ||
|
|
575
|
+
content.includes('hosts:') ||
|
|
576
|
+
content.includes('tasks:') ||
|
|
577
|
+
content.includes('roles:'));
|
|
578
|
+
}
|
|
579
|
+
isKubernetesFile(filename, content) {
|
|
580
|
+
return (filename.includes('k8s') ||
|
|
581
|
+
filename.includes('kubernetes') ||
|
|
582
|
+
content.includes('apiVersion:') ||
|
|
583
|
+
content.includes('kind:'));
|
|
584
|
+
}
|
|
585
|
+
isDockerComposeFile(filename, content) {
|
|
586
|
+
return (filename.includes('docker-compose') ||
|
|
587
|
+
(content.includes('version:') && content.includes('services:')));
|
|
588
|
+
}
|
|
589
|
+
async analyzeAnsibleYAML(_node, lines, result) {
|
|
590
|
+
const content = lines.join('\n');
|
|
591
|
+
// Detect Ansible tasks and roles
|
|
592
|
+
if (content.includes('tasks:')) {
|
|
593
|
+
const taskMatches = content.match(/- name:\s*(.+)/g) || [];
|
|
594
|
+
taskMatches.forEach(match => {
|
|
595
|
+
const taskName = match.replace(/- name:\s*/, '').trim();
|
|
596
|
+
result.functions.push({
|
|
597
|
+
name: taskName,
|
|
598
|
+
type: 'task',
|
|
599
|
+
parameters: [],
|
|
600
|
+
location: { line: this.findLineNumber(lines, match), column: 0 },
|
|
601
|
+
complexity: 1,
|
|
602
|
+
securitySensitive: this.isSecuritySensitiveTask(taskName),
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
// Detect role imports
|
|
607
|
+
const roleMatches = content.match(/roles?:\s*\n([\s\S]*?)(?=\n\w|$)/g) || [];
|
|
608
|
+
roleMatches.forEach(roleBlock => {
|
|
609
|
+
const roles = roleBlock.match(/- ([\w.-]+)/g) || [];
|
|
610
|
+
roles.forEach(role => {
|
|
611
|
+
const roleName = role.replace(/- /, '');
|
|
612
|
+
result.imports.push({
|
|
613
|
+
module: roleName,
|
|
614
|
+
type: 'role',
|
|
615
|
+
location: { line: this.findLineNumber(lines, role), column: 0 },
|
|
616
|
+
isExternal: !roleName.startsWith('./'),
|
|
617
|
+
isDangerous: this.isDangerousAnsibleRole(roleName),
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
// Check for security-sensitive variables
|
|
622
|
+
const varMatches = content.match(/\b\w*(?:password|secret|key|token)\w*:\s*["']?([^"'\n]+)["']?/gi) || [];
|
|
623
|
+
varMatches.forEach(varMatch => {
|
|
624
|
+
const [fullMatch, value] = varMatch.match(/([\w_]+):\s*["']?([^"'\n]+)["']?/) || [];
|
|
625
|
+
if (fullMatch && value) {
|
|
626
|
+
result.secrets.push({
|
|
627
|
+
type: 'credential',
|
|
628
|
+
value: value,
|
|
629
|
+
location: { line: this.findLineNumber(lines, fullMatch), column: 0 },
|
|
630
|
+
confidence: 0.7,
|
|
631
|
+
context: 'Ansible variable',
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
async analyzeKubernetesYAML(_node, lines, result) {
|
|
637
|
+
const content = lines.join('\n');
|
|
638
|
+
// Extract Kubernetes resources
|
|
639
|
+
const resourceMatches = content.match(/kind:\s*(\w+)/g) || [];
|
|
640
|
+
const apiVersionMatches = content.match(/apiVersion:\s*([\w/]+)/g) || [];
|
|
641
|
+
const nameMatches = content.match(/name:\s*([\w.-]+)/g) || [];
|
|
642
|
+
if (resourceMatches.length > 0) {
|
|
643
|
+
resourceMatches.forEach((kindMatch, index) => {
|
|
644
|
+
const kind = kindMatch.replace(/kind:\s*/, '');
|
|
645
|
+
const apiVersion = apiVersionMatches[index]?.replace(/apiVersion:\s*/, '') || 'v1';
|
|
646
|
+
const name = nameMatches[index]?.replace(/name:\s*/, '') || 'unnamed';
|
|
647
|
+
const securityRisks = this.analyzeKubernetesSecurityRisks(content, kind);
|
|
648
|
+
result.infraStructure.push({
|
|
649
|
+
resourceType: kind.toLowerCase(),
|
|
650
|
+
name: name,
|
|
651
|
+
provider: 'kubernetes',
|
|
652
|
+
configuration: { apiVersion, kind },
|
|
653
|
+
securityRisks,
|
|
654
|
+
location: { line: this.findLineNumber(lines, kindMatch), column: 0 },
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
// Check for security contexts and privileged containers
|
|
659
|
+
if (content.includes('privileged: true')) {
|
|
660
|
+
result.securityIssues.push({
|
|
661
|
+
type: 'privilege_escalation',
|
|
662
|
+
severity: 'high',
|
|
663
|
+
message: 'Container running in privileged mode',
|
|
664
|
+
location: { line: this.findLineNumber(lines, 'privileged: true'), column: 0 },
|
|
665
|
+
suggestion: 'Remove privileged: true and use specific capabilities instead',
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
// Check for missing resource limits
|
|
669
|
+
if (content.includes('containers:') && !content.includes('resources:')) {
|
|
670
|
+
result.securityIssues.push({
|
|
671
|
+
type: 'insecure_config',
|
|
672
|
+
severity: 'medium',
|
|
673
|
+
message: 'Container missing resource limits',
|
|
674
|
+
location: { line: this.findLineNumber(lines, 'containers:'), column: 0 },
|
|
675
|
+
suggestion: 'Add resource requests and limits to prevent resource exhaustion',
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
// Detect secrets in environment variables
|
|
679
|
+
const envMatches = content.match(/env:\s*\n([\s\S]*?)(?=\n\s*\w|$)/g) || [];
|
|
680
|
+
envMatches.forEach(envBlock => {
|
|
681
|
+
const secretRefs = envBlock.match(/valueFrom:\s*\n\s*secretKeyRef:/g) || [];
|
|
682
|
+
if (secretRefs.length === 0 && envBlock.includes('value:')) {
|
|
683
|
+
// Direct environment values might contain secrets
|
|
684
|
+
const directValues = envBlock.match(/- name:\s*(\w*(?:PASSWORD|SECRET|KEY|TOKEN)\w*)\s*\n\s*value:\s*([^\n]+)/gi) || [];
|
|
685
|
+
directValues.forEach(match => {
|
|
686
|
+
const [, envName, envValue] = match.match(/- name:\s*(\w+)\s*\n\s*value:\s*([^\n]+)/i) || [];
|
|
687
|
+
if (envName && envValue) {
|
|
688
|
+
result.secrets.push({
|
|
689
|
+
type: 'credential',
|
|
690
|
+
value: envValue,
|
|
691
|
+
location: { line: this.findLineNumber(lines, match), column: 0 },
|
|
692
|
+
confidence: 0.8,
|
|
693
|
+
context: `Kubernetes env var: ${envName}`,
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
async analyzeDockerComposeYAML(_node, lines, result) {
|
|
701
|
+
const content = lines.join('\n');
|
|
702
|
+
// Extract services
|
|
703
|
+
const serviceMatches = content.match(/^\s{2}(\w[\w.-]*):$/gm) || [];
|
|
704
|
+
serviceMatches.forEach(serviceMatch => {
|
|
705
|
+
const serviceName = serviceMatch.replace(/^\s{2}/, '').replace(/:$/, '');
|
|
706
|
+
result.infraStructure.push({
|
|
707
|
+
resourceType: 'docker_service',
|
|
708
|
+
name: serviceName,
|
|
709
|
+
provider: 'docker',
|
|
710
|
+
configuration: {},
|
|
711
|
+
securityRisks: this.analyzeDockerComposeSecurityRisks(content, serviceName),
|
|
712
|
+
location: { line: this.findLineNumber(lines, serviceMatch), column: 0 },
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
// Check for exposed ports
|
|
716
|
+
const portMatches = content.match(/ports:\s*\n([\s\S]*?)(?=\n\s*\w|$)/g) || [];
|
|
717
|
+
portMatches.forEach(portBlock => {
|
|
718
|
+
if (portBlock.includes('"80:') || portBlock.includes('"443:')) {
|
|
719
|
+
const webPorts = portBlock.match(/"(80|443):[^"]+"/g) || [];
|
|
720
|
+
webPorts.forEach(port => {
|
|
721
|
+
result.securityIssues.push({
|
|
722
|
+
type: 'insecure_config',
|
|
723
|
+
severity: 'medium',
|
|
724
|
+
message: `Web port ${port} exposed without TLS configuration`,
|
|
725
|
+
location: { line: this.findLineNumber(lines, port), column: 0 },
|
|
726
|
+
suggestion: 'Ensure proper TLS termination and security headers',
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
// Check for environment variables with secrets
|
|
732
|
+
const envMatches = content.match(/environment:\s*\n([\s\S]*?)(?=\n\s*\w|$)/g) || [];
|
|
733
|
+
envMatches.forEach(envBlock => {
|
|
734
|
+
const secretVars = envBlock.match(/\s*-?\s*(\w*(?:PASSWORD|SECRET|KEY|TOKEN|API_KEY)\w*)\s*[:=]\s*([^\n]+)/gi) || [];
|
|
735
|
+
secretVars.forEach(secretVar => {
|
|
736
|
+
const [, varName, varValue] = secretVar.match(/\s*-?\s*(\w+)\s*[:=]\s*([^\n]+)/i) || [];
|
|
737
|
+
if (varName && varValue && !varValue.includes('${')) {
|
|
738
|
+
result.secrets.push({
|
|
739
|
+
type: 'credential',
|
|
740
|
+
value: varValue,
|
|
741
|
+
location: { line: this.findLineNumber(lines, secretVar), column: 0 },
|
|
742
|
+
confidence: 0.8,
|
|
743
|
+
context: `Docker Compose env var: ${varName}`,
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
});
|
|
748
|
+
// Check for privileged containers
|
|
749
|
+
if (content.includes('privileged: true')) {
|
|
750
|
+
result.securityIssues.push({
|
|
751
|
+
type: 'privilege_escalation',
|
|
752
|
+
severity: 'high',
|
|
753
|
+
message: 'Container running in privileged mode',
|
|
754
|
+
location: { line: this.findLineNumber(lines, 'privileged: true'), column: 0 },
|
|
755
|
+
suggestion: 'Remove privileged mode and use specific capabilities instead',
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
analyzeTerraformSecurity(resource) {
|
|
760
|
+
const risks = [];
|
|
761
|
+
// Check for common security issues in Terraform
|
|
762
|
+
const resourceText = resource.text.toLowerCase();
|
|
763
|
+
if (resourceText.includes('0.0.0.0/0')) {
|
|
764
|
+
risks.push('Open to all IP addresses');
|
|
765
|
+
}
|
|
766
|
+
if (resourceText.includes('port = 22') && resourceText.includes('0.0.0.0/0')) {
|
|
767
|
+
risks.push('SSH port open to internet');
|
|
768
|
+
}
|
|
769
|
+
return risks;
|
|
770
|
+
}
|
|
771
|
+
// Helper methods for the enhanced YAML analysis
|
|
772
|
+
findLineNumber(lines, searchText) {
|
|
773
|
+
for (let i = 0; i < lines.length; i++) {
|
|
774
|
+
if (lines[i]?.includes(searchText)) {
|
|
775
|
+
return i + 1;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return 1;
|
|
779
|
+
}
|
|
780
|
+
isSecuritySensitiveTask(taskName) {
|
|
781
|
+
const sensitiveKeywords = [
|
|
782
|
+
'password',
|
|
783
|
+
'secret',
|
|
784
|
+
'key',
|
|
785
|
+
'token',
|
|
786
|
+
'auth',
|
|
787
|
+
'login',
|
|
788
|
+
'credential',
|
|
789
|
+
'sudo',
|
|
790
|
+
'become',
|
|
791
|
+
'privilege',
|
|
792
|
+
'root',
|
|
793
|
+
'admin',
|
|
794
|
+
'ssh',
|
|
795
|
+
'ssl',
|
|
796
|
+
'tls',
|
|
797
|
+
];
|
|
798
|
+
const lowerTaskName = taskName.toLowerCase();
|
|
799
|
+
return sensitiveKeywords.some(keyword => lowerTaskName.includes(keyword));
|
|
800
|
+
}
|
|
801
|
+
isDangerousAnsibleRole(roleName) {
|
|
802
|
+
const dangerousRoles = [
|
|
803
|
+
'shell',
|
|
804
|
+
'command',
|
|
805
|
+
'raw',
|
|
806
|
+
'script',
|
|
807
|
+
'sudo',
|
|
808
|
+
'become',
|
|
809
|
+
'privilege',
|
|
810
|
+
'firewall',
|
|
811
|
+
'iptables',
|
|
812
|
+
'selinux',
|
|
813
|
+
'apparmor',
|
|
814
|
+
];
|
|
815
|
+
const lowerRoleName = roleName.toLowerCase();
|
|
816
|
+
return dangerousRoles.some(dangerous => lowerRoleName.includes(dangerous));
|
|
817
|
+
}
|
|
818
|
+
analyzeKubernetesSecurityRisks(content, kind) {
|
|
819
|
+
const risks = [];
|
|
820
|
+
const lowerContent = content.toLowerCase();
|
|
821
|
+
// Check for privileged containers
|
|
822
|
+
if (lowerContent.includes('privileged: true')) {
|
|
823
|
+
risks.push('Privileged container detected');
|
|
824
|
+
}
|
|
825
|
+
// Check for host network
|
|
826
|
+
if (lowerContent.includes('hostnetwork: true')) {
|
|
827
|
+
risks.push('Host network access enabled');
|
|
828
|
+
}
|
|
829
|
+
// Check for host PID
|
|
830
|
+
if (lowerContent.includes('hostpid: true')) {
|
|
831
|
+
risks.push('Host PID namespace access enabled');
|
|
832
|
+
}
|
|
833
|
+
// Check for missing security context
|
|
834
|
+
if (kind.toLowerCase() === 'deployment' && !lowerContent.includes('securitycontext')) {
|
|
835
|
+
risks.push('Missing security context configuration');
|
|
836
|
+
}
|
|
837
|
+
// Check for root user
|
|
838
|
+
if (lowerContent.includes('runasuser: 0')) {
|
|
839
|
+
risks.push('Container running as root user');
|
|
840
|
+
}
|
|
841
|
+
// Check for missing resource limits
|
|
842
|
+
if (lowerContent.includes('containers:') && !lowerContent.includes('resources:')) {
|
|
843
|
+
risks.push('Missing resource limits');
|
|
844
|
+
}
|
|
845
|
+
return risks;
|
|
846
|
+
}
|
|
847
|
+
analyzeDockerComposeSecurityRisks(content, serviceName) {
|
|
848
|
+
const risks = [];
|
|
849
|
+
// Extract service-specific content
|
|
850
|
+
const serviceRegex = new RegExp(`\\s{2}${serviceName}:\\s*\\n([\\s\\S]*?)(?=\\n\\s{2}\\w|$)`, 'i');
|
|
851
|
+
const serviceMatch = content.match(serviceRegex);
|
|
852
|
+
const serviceContent = serviceMatch?.[1]?.toLowerCase() || '';
|
|
853
|
+
// Check for privileged mode
|
|
854
|
+
if (serviceContent.includes('privileged: true')) {
|
|
855
|
+
risks.push('Service running in privileged mode');
|
|
856
|
+
}
|
|
857
|
+
// Check for host network
|
|
858
|
+
if (serviceContent.includes('network_mode: host')) {
|
|
859
|
+
risks.push('Service using host network');
|
|
860
|
+
}
|
|
861
|
+
// Check for volume mounts to sensitive paths
|
|
862
|
+
const sensitiveVolumes = ['/etc', '/var/run/docker.sock', '/proc', '/sys'];
|
|
863
|
+
sensitiveVolumes.forEach(path => {
|
|
864
|
+
if (serviceContent.includes(`${path}:`)) {
|
|
865
|
+
risks.push(`Mounting sensitive host path: ${path}`);
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
// Check for exposed ports without proper configuration
|
|
869
|
+
if (serviceContent.includes('ports:') && serviceContent.includes('"80:')) {
|
|
870
|
+
risks.push('HTTP port exposed without TLS');
|
|
871
|
+
}
|
|
872
|
+
// Check for missing restart policy
|
|
873
|
+
if (!serviceContent.includes('restart:')) {
|
|
874
|
+
risks.push('Missing restart policy');
|
|
875
|
+
}
|
|
876
|
+
return risks;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
// Factory function for easy usage
|
|
880
|
+
export function createTreeSitterAnalyzer() {
|
|
881
|
+
return new TreeSitterAnalyzer();
|
|
882
|
+
}
|
|
883
|
+
// Export default analyzer instance
|
|
884
|
+
export const analyzer = new TreeSitterAnalyzer();
|
|
885
|
+
//# sourceMappingURL=tree-sitter-analyzer.js.map
|