tree-sitter-analyzer 0.9.1__py3-none-any.whl → 0.9.3__py3-none-any.whl
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.
Potentially problematic release.
This version of tree-sitter-analyzer might be problematic. Click here for more details.
- tree_sitter_analyzer/__init__.py +132 -132
- tree_sitter_analyzer/__main__.py +11 -11
- tree_sitter_analyzer/api.py +533 -533
- tree_sitter_analyzer/cli/__init__.py +39 -39
- tree_sitter_analyzer/cli/__main__.py +12 -12
- tree_sitter_analyzer/cli/commands/__init__.py +26 -26
- tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
- tree_sitter_analyzer/cli/commands/base_command.py +181 -178
- tree_sitter_analyzer/cli/commands/structure_command.py +138 -138
- tree_sitter_analyzer/cli/commands/summary_command.py +101 -101
- tree_sitter_analyzer/cli_main.py +7 -3
- tree_sitter_analyzer/core/__init__.py +15 -15
- tree_sitter_analyzer/core/analysis_engine.py +91 -87
- tree_sitter_analyzer/core/cache_service.py +320 -320
- tree_sitter_analyzer/core/engine.py +566 -566
- tree_sitter_analyzer/core/parser.py +293 -293
- tree_sitter_analyzer/encoding_utils.py +459 -459
- tree_sitter_analyzer/file_handler.py +210 -210
- tree_sitter_analyzer/formatters/__init__.py +1 -1
- tree_sitter_analyzer/formatters/base_formatter.py +167 -167
- tree_sitter_analyzer/formatters/formatter_factory.py +78 -78
- tree_sitter_analyzer/formatters/java_formatter.py +18 -18
- tree_sitter_analyzer/formatters/python_formatter.py +19 -19
- tree_sitter_analyzer/interfaces/__init__.py +9 -9
- tree_sitter_analyzer/interfaces/cli.py +528 -528
- tree_sitter_analyzer/interfaces/cli_adapter.py +344 -343
- tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -206
- tree_sitter_analyzer/language_detector.py +53 -53
- tree_sitter_analyzer/languages/__init__.py +10 -10
- tree_sitter_analyzer/languages/java_plugin.py +1 -1
- tree_sitter_analyzer/languages/javascript_plugin.py +446 -446
- tree_sitter_analyzer/languages/python_plugin.py +755 -755
- tree_sitter_analyzer/mcp/__init__.py +34 -45
- tree_sitter_analyzer/mcp/resources/__init__.py +44 -44
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -209
- tree_sitter_analyzer/mcp/server.py +623 -568
- tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +681 -673
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -247
- tree_sitter_analyzer/mcp/tools/base_tool.py +54 -54
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +310 -308
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +386 -379
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +563 -559
- tree_sitter_analyzer/mcp/utils/__init__.py +107 -107
- tree_sitter_analyzer/models.py +10 -10
- tree_sitter_analyzer/output_manager.py +253 -253
- tree_sitter_analyzer/plugins/__init__.py +280 -280
- tree_sitter_analyzer/plugins/base.py +529 -529
- tree_sitter_analyzer/plugins/manager.py +379 -379
- tree_sitter_analyzer/project_detector.py +330 -317
- tree_sitter_analyzer/queries/__init__.py +26 -26
- tree_sitter_analyzer/queries/java.py +391 -391
- tree_sitter_analyzer/queries/javascript.py +148 -148
- tree_sitter_analyzer/queries/python.py +285 -285
- tree_sitter_analyzer/queries/typescript.py +229 -229
- tree_sitter_analyzer/query_loader.py +257 -257
- tree_sitter_analyzer/security/boundary_manager.py +57 -51
- tree_sitter_analyzer/security/validator.py +246 -241
- tree_sitter_analyzer/utils.py +294 -277
- {tree_sitter_analyzer-0.9.1.dist-info → tree_sitter_analyzer-0.9.3.dist-info}/METADATA +13 -13
- tree_sitter_analyzer-0.9.3.dist-info/RECORD +77 -0
- {tree_sitter_analyzer-0.9.1.dist-info → tree_sitter_analyzer-0.9.3.dist-info}/entry_points.txt +1 -0
- tree_sitter_analyzer-0.9.1.dist-info/RECORD +0 -77
- {tree_sitter_analyzer-0.9.1.dist-info → tree_sitter_analyzer-0.9.3.dist-info}/WHEEL +0 -0
|
@@ -8,7 +8,6 @@ outside the designated project directory.
|
|
|
8
8
|
|
|
9
9
|
import os
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Optional, Set
|
|
12
11
|
|
|
13
12
|
from ..exceptions import SecurityError
|
|
14
13
|
from ..utils import log_debug, log_info, log_warning
|
|
@@ -17,10 +16,10 @@ from ..utils import log_debug, log_info, log_warning
|
|
|
17
16
|
class ProjectBoundaryManager:
|
|
18
17
|
"""
|
|
19
18
|
Project boundary manager for access control.
|
|
20
|
-
|
|
19
|
+
|
|
21
20
|
This class enforces strict boundaries around project directories
|
|
22
21
|
to prevent unauthorized file access outside the project scope.
|
|
23
|
-
|
|
22
|
+
|
|
24
23
|
Features:
|
|
25
24
|
- Real path resolution for symlink protection
|
|
26
25
|
- Configurable allowed directories
|
|
@@ -31,59 +30,59 @@ class ProjectBoundaryManager:
|
|
|
31
30
|
def __init__(self, project_root: str) -> None:
|
|
32
31
|
"""
|
|
33
32
|
Initialize project boundary manager.
|
|
34
|
-
|
|
33
|
+
|
|
35
34
|
Args:
|
|
36
35
|
project_root: Root directory of the project
|
|
37
|
-
|
|
36
|
+
|
|
38
37
|
Raises:
|
|
39
38
|
SecurityError: If project root is invalid
|
|
40
39
|
"""
|
|
41
40
|
if not project_root:
|
|
42
41
|
raise SecurityError("Project root cannot be empty")
|
|
43
|
-
|
|
42
|
+
|
|
44
43
|
if not os.path.exists(project_root):
|
|
45
44
|
raise SecurityError(f"Project root does not exist: {project_root}")
|
|
46
|
-
|
|
45
|
+
|
|
47
46
|
if not os.path.isdir(project_root):
|
|
48
47
|
raise SecurityError(f"Project root is not a directory: {project_root}")
|
|
49
|
-
|
|
48
|
+
|
|
50
49
|
# Store real path to prevent symlink attacks
|
|
51
50
|
self.project_root = os.path.realpath(project_root)
|
|
52
|
-
self.allowed_directories:
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
self.allowed_directories: set[str] = {self.project_root}
|
|
52
|
+
|
|
53
|
+
log_debug(f"ProjectBoundaryManager initialized with root: {self.project_root}")
|
|
55
54
|
|
|
56
55
|
def add_allowed_directory(self, directory: str) -> None:
|
|
57
56
|
"""
|
|
58
57
|
Add an additional allowed directory.
|
|
59
|
-
|
|
58
|
+
|
|
60
59
|
Args:
|
|
61
60
|
directory: Directory path to allow access to
|
|
62
|
-
|
|
61
|
+
|
|
63
62
|
Raises:
|
|
64
63
|
SecurityError: If directory is invalid
|
|
65
64
|
"""
|
|
66
65
|
if not directory:
|
|
67
66
|
raise SecurityError("Directory cannot be empty")
|
|
68
|
-
|
|
67
|
+
|
|
69
68
|
if not os.path.exists(directory):
|
|
70
69
|
raise SecurityError(f"Directory does not exist: {directory}")
|
|
71
|
-
|
|
70
|
+
|
|
72
71
|
if not os.path.isdir(directory):
|
|
73
72
|
raise SecurityError(f"Path is not a directory: {directory}")
|
|
74
|
-
|
|
73
|
+
|
|
75
74
|
real_dir = os.path.realpath(directory)
|
|
76
75
|
self.allowed_directories.add(real_dir)
|
|
77
|
-
|
|
76
|
+
|
|
78
77
|
log_info(f"Added allowed directory: {real_dir}")
|
|
79
78
|
|
|
80
79
|
def is_within_project(self, file_path: str) -> bool:
|
|
81
80
|
"""
|
|
82
81
|
Check if file path is within project boundaries.
|
|
83
|
-
|
|
82
|
+
|
|
84
83
|
Args:
|
|
85
84
|
file_path: File path to check
|
|
86
|
-
|
|
85
|
+
|
|
87
86
|
Returns:
|
|
88
87
|
True if path is within allowed boundaries
|
|
89
88
|
"""
|
|
@@ -91,58 +90,61 @@ class ProjectBoundaryManager:
|
|
|
91
90
|
if not file_path:
|
|
92
91
|
log_warning("Empty file path provided to boundary check")
|
|
93
92
|
return False
|
|
94
|
-
|
|
93
|
+
|
|
95
94
|
# Resolve real path to handle symlinks
|
|
96
95
|
real_path = os.path.realpath(file_path)
|
|
97
|
-
|
|
96
|
+
|
|
98
97
|
# Check against all allowed directories
|
|
99
98
|
for allowed_dir in self.allowed_directories:
|
|
100
|
-
if
|
|
99
|
+
if (
|
|
100
|
+
real_path.startswith(allowed_dir + os.sep)
|
|
101
|
+
or real_path == allowed_dir
|
|
102
|
+
):
|
|
101
103
|
log_debug(f"File path within boundaries: {file_path}")
|
|
102
104
|
return True
|
|
103
|
-
|
|
105
|
+
|
|
104
106
|
log_warning(f"File path outside boundaries: {file_path} -> {real_path}")
|
|
105
107
|
return False
|
|
106
|
-
|
|
108
|
+
|
|
107
109
|
except Exception as e:
|
|
108
110
|
log_warning(f"Boundary check error for {file_path}: {e}")
|
|
109
111
|
return False
|
|
110
112
|
|
|
111
|
-
def get_relative_path(self, file_path: str) ->
|
|
113
|
+
def get_relative_path(self, file_path: str) -> str | None:
|
|
112
114
|
"""
|
|
113
115
|
Get relative path from project root if within boundaries.
|
|
114
|
-
|
|
116
|
+
|
|
115
117
|
Args:
|
|
116
118
|
file_path: File path to convert
|
|
117
|
-
|
|
119
|
+
|
|
118
120
|
Returns:
|
|
119
121
|
Relative path from project root, or None if outside boundaries
|
|
120
122
|
"""
|
|
121
123
|
if not self.is_within_project(file_path):
|
|
122
124
|
return None
|
|
123
|
-
|
|
125
|
+
|
|
124
126
|
try:
|
|
125
127
|
real_path = os.path.realpath(file_path)
|
|
126
128
|
rel_path = os.path.relpath(real_path, self.project_root)
|
|
127
|
-
|
|
129
|
+
|
|
128
130
|
# Ensure relative path doesn't start with ..
|
|
129
131
|
if rel_path.startswith(".."):
|
|
130
132
|
log_warning(f"Relative path calculation failed: {rel_path}")
|
|
131
133
|
return None
|
|
132
|
-
|
|
134
|
+
|
|
133
135
|
return rel_path
|
|
134
|
-
|
|
136
|
+
|
|
135
137
|
except Exception as e:
|
|
136
138
|
log_warning(f"Relative path calculation error: {e}")
|
|
137
139
|
return None
|
|
138
140
|
|
|
139
|
-
def validate_and_resolve_path(self, file_path: str) ->
|
|
141
|
+
def validate_and_resolve_path(self, file_path: str) -> str | None:
|
|
140
142
|
"""
|
|
141
143
|
Validate path and return resolved absolute path if within boundaries.
|
|
142
|
-
|
|
144
|
+
|
|
143
145
|
Args:
|
|
144
146
|
file_path: File path to validate and resolve
|
|
145
|
-
|
|
147
|
+
|
|
146
148
|
Returns:
|
|
147
149
|
Resolved absolute path if valid, None otherwise
|
|
148
150
|
"""
|
|
@@ -152,22 +154,22 @@ class ProjectBoundaryManager:
|
|
|
152
154
|
full_path = os.path.join(self.project_root, file_path)
|
|
153
155
|
else:
|
|
154
156
|
full_path = file_path
|
|
155
|
-
|
|
157
|
+
|
|
156
158
|
# Check boundaries
|
|
157
159
|
if not self.is_within_project(full_path):
|
|
158
160
|
return None
|
|
159
|
-
|
|
161
|
+
|
|
160
162
|
# Return real path
|
|
161
163
|
return os.path.realpath(full_path)
|
|
162
|
-
|
|
164
|
+
|
|
163
165
|
except Exception as e:
|
|
164
166
|
log_warning(f"Path validation error: {e}")
|
|
165
167
|
return None
|
|
166
168
|
|
|
167
|
-
def list_allowed_directories(self) ->
|
|
169
|
+
def list_allowed_directories(self) -> set[str]:
|
|
168
170
|
"""
|
|
169
171
|
Get list of all allowed directories.
|
|
170
|
-
|
|
172
|
+
|
|
171
173
|
Returns:
|
|
172
174
|
Set of allowed directory paths
|
|
173
175
|
"""
|
|
@@ -176,33 +178,37 @@ class ProjectBoundaryManager:
|
|
|
176
178
|
def is_symlink_safe(self, file_path: str) -> bool:
|
|
177
179
|
"""
|
|
178
180
|
Check if file path is safe from symlink attacks.
|
|
179
|
-
|
|
181
|
+
|
|
180
182
|
Args:
|
|
181
183
|
file_path: File path to check
|
|
182
|
-
|
|
184
|
+
|
|
183
185
|
Returns:
|
|
184
186
|
True if path is safe from symlink attacks
|
|
185
187
|
"""
|
|
186
188
|
try:
|
|
187
189
|
if not os.path.exists(file_path):
|
|
188
190
|
return True # Non-existent files are safe
|
|
189
|
-
|
|
191
|
+
|
|
190
192
|
# Check if any component in the path is a symlink
|
|
191
193
|
path_parts = Path(file_path).parts
|
|
192
194
|
current_path = ""
|
|
193
|
-
|
|
195
|
+
|
|
194
196
|
for part in path_parts:
|
|
195
|
-
current_path =
|
|
196
|
-
|
|
197
|
+
current_path = (
|
|
198
|
+
os.path.join(current_path, part) if current_path else part
|
|
199
|
+
)
|
|
200
|
+
|
|
197
201
|
if os.path.islink(current_path):
|
|
198
202
|
# Check if symlink target is within boundaries
|
|
199
203
|
target = os.path.realpath(current_path)
|
|
200
204
|
if not self.is_within_project(target):
|
|
201
|
-
log_warning(
|
|
205
|
+
log_warning(
|
|
206
|
+
f"Unsafe symlink detected: {current_path} -> {target}"
|
|
207
|
+
)
|
|
202
208
|
return False
|
|
203
|
-
|
|
209
|
+
|
|
204
210
|
return True
|
|
205
|
-
|
|
211
|
+
|
|
206
212
|
except Exception as e:
|
|
207
213
|
log_warning(f"Symlink safety check error: {e}")
|
|
208
214
|
return False
|
|
@@ -210,16 +216,16 @@ class ProjectBoundaryManager:
|
|
|
210
216
|
def audit_access(self, file_path: str, operation: str) -> None:
|
|
211
217
|
"""
|
|
212
218
|
Log file access for security auditing.
|
|
213
|
-
|
|
219
|
+
|
|
214
220
|
Args:
|
|
215
221
|
file_path: File path being accessed
|
|
216
222
|
operation: Type of operation (read, write, analyze, etc.)
|
|
217
223
|
"""
|
|
218
224
|
is_within = self.is_within_project(file_path)
|
|
219
225
|
status = "ALLOWED" if is_within else "DENIED"
|
|
220
|
-
|
|
226
|
+
|
|
221
227
|
log_info(f"AUDIT: {status} {operation} access to {file_path}")
|
|
222
|
-
|
|
228
|
+
|
|
223
229
|
if not is_within:
|
|
224
230
|
log_warning(f"SECURITY: Unauthorized access attempt to {file_path}")
|
|
225
231
|
|