tree-sitter-analyzer 0.9.3__py3-none-any.whl → 0.9.5__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.

Files changed (33) hide show
  1. tree_sitter_analyzer/cli/commands/default_command.py +18 -18
  2. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -141
  3. tree_sitter_analyzer/cli/commands/query_command.py +92 -88
  4. tree_sitter_analyzer/cli/commands/table_command.py +235 -235
  5. tree_sitter_analyzer/cli/info_commands.py +121 -121
  6. tree_sitter_analyzer/cli_main.py +307 -307
  7. tree_sitter_analyzer/core/analysis_engine.py +584 -584
  8. tree_sitter_analyzer/core/cache_service.py +5 -4
  9. tree_sitter_analyzer/core/query.py +502 -502
  10. tree_sitter_analyzer/encoding_utils.py +9 -2
  11. tree_sitter_analyzer/exceptions.py +400 -406
  12. tree_sitter_analyzer/formatters/java_formatter.py +291 -291
  13. tree_sitter_analyzer/formatters/python_formatter.py +259 -259
  14. tree_sitter_analyzer/interfaces/mcp_server.py +426 -425
  15. tree_sitter_analyzer/language_detector.py +398 -398
  16. tree_sitter_analyzer/language_loader.py +224 -224
  17. tree_sitter_analyzer/languages/java_plugin.py +1202 -1202
  18. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +559 -555
  19. tree_sitter_analyzer/mcp/server.py +30 -9
  20. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +21 -4
  21. tree_sitter_analyzer/mcp/tools/table_format_tool.py +22 -4
  22. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -567
  23. tree_sitter_analyzer/models.py +470 -470
  24. tree_sitter_analyzer/security/__init__.py +22 -22
  25. tree_sitter_analyzer/security/boundary_manager.py +251 -243
  26. tree_sitter_analyzer/security/regex_checker.py +297 -292
  27. tree_sitter_analyzer/table_formatter.py +708 -652
  28. tree_sitter_analyzer/utils.py +61 -19
  29. tree_sitter_analyzer-0.9.5.dist-info/METADATA +567 -0
  30. {tree_sitter_analyzer-0.9.3.dist-info → tree_sitter_analyzer-0.9.5.dist-info}/RECORD +32 -32
  31. tree_sitter_analyzer-0.9.3.dist-info/METADATA +0 -409
  32. {tree_sitter_analyzer-0.9.3.dist-info → tree_sitter_analyzer-0.9.5.dist-info}/WHEEL +0 -0
  33. {tree_sitter_analyzer-0.9.3.dist-info → tree_sitter_analyzer-0.9.5.dist-info}/entry_points.txt +0 -0
@@ -1,22 +1,22 @@
1
- #!/usr/bin/env python3
2
- """
3
- Security module for Tree-sitter Analyzer
4
-
5
- This module provides unified security validation and protection mechanisms
6
- for file path validation, regex pattern safety, and project boundary control.
7
-
8
- Architecture:
9
- - SecurityValidator: Unified validation framework
10
- - ProjectBoundaryManager: Project access control
11
- - RegexSafetyChecker: ReDoS attack prevention
12
- """
13
-
14
- from .boundary_manager import ProjectBoundaryManager
15
- from .regex_checker import RegexSafetyChecker
16
- from .validator import SecurityValidator
17
-
18
- __all__ = [
19
- "SecurityValidator",
20
- "ProjectBoundaryManager",
21
- "RegexSafetyChecker",
22
- ]
1
+ #!/usr/bin/env python3
2
+ """
3
+ Security module for Tree-sitter Analyzer
4
+
5
+ This module provides unified security validation and protection mechanisms
6
+ for file path validation, regex pattern safety, and project boundary control.
7
+
8
+ Architecture:
9
+ - SecurityValidator: Unified validation framework
10
+ - ProjectBoundaryManager: Project access control
11
+ - RegexSafetyChecker: ReDoS attack prevention
12
+ """
13
+
14
+ from .boundary_manager import ProjectBoundaryManager
15
+ from .regex_checker import RegexSafetyChecker
16
+ from .validator import SecurityValidator
17
+
18
+ __all__ = [
19
+ "SecurityValidator",
20
+ "ProjectBoundaryManager",
21
+ "RegexSafetyChecker",
22
+ ]
@@ -1,243 +1,251 @@
1
- #!/usr/bin/env python3
2
- """
3
- Project Boundary Manager for Tree-sitter Analyzer
4
-
5
- Provides strict project boundary control to prevent access to files
6
- outside the designated project directory.
7
- """
8
-
9
- import os
10
- from pathlib import Path
11
-
12
- from ..exceptions import SecurityError
13
- from ..utils import log_debug, log_info, log_warning
14
-
15
-
16
- class ProjectBoundaryManager:
17
- """
18
- Project boundary manager for access control.
19
-
20
- This class enforces strict boundaries around project directories
21
- to prevent unauthorized file access outside the project scope.
22
-
23
- Features:
24
- - Real path resolution for symlink protection
25
- - Configurable allowed directories
26
- - Comprehensive boundary checking
27
- - Audit logging for security events
28
- """
29
-
30
- def __init__(self, project_root: str) -> None:
31
- """
32
- Initialize project boundary manager.
33
-
34
- Args:
35
- project_root: Root directory of the project
36
-
37
- Raises:
38
- SecurityError: If project root is invalid
39
- """
40
- if not project_root:
41
- raise SecurityError("Project root cannot be empty")
42
-
43
- if not os.path.exists(project_root):
44
- raise SecurityError(f"Project root does not exist: {project_root}")
45
-
46
- if not os.path.isdir(project_root):
47
- raise SecurityError(f"Project root is not a directory: {project_root}")
48
-
49
- # Store real path to prevent symlink attacks
50
- self.project_root = os.path.realpath(project_root)
51
- self.allowed_directories: set[str] = {self.project_root}
52
-
53
- log_debug(f"ProjectBoundaryManager initialized with root: {self.project_root}")
54
-
55
- def add_allowed_directory(self, directory: str) -> None:
56
- """
57
- Add an additional allowed directory.
58
-
59
- Args:
60
- directory: Directory path to allow access to
61
-
62
- Raises:
63
- SecurityError: If directory is invalid
64
- """
65
- if not directory:
66
- raise SecurityError("Directory cannot be empty")
67
-
68
- if not os.path.exists(directory):
69
- raise SecurityError(f"Directory does not exist: {directory}")
70
-
71
- if not os.path.isdir(directory):
72
- raise SecurityError(f"Path is not a directory: {directory}")
73
-
74
- real_dir = os.path.realpath(directory)
75
- self.allowed_directories.add(real_dir)
76
-
77
- log_info(f"Added allowed directory: {real_dir}")
78
-
79
- def is_within_project(self, file_path: str) -> bool:
80
- """
81
- Check if file path is within project boundaries.
82
-
83
- Args:
84
- file_path: File path to check
85
-
86
- Returns:
87
- True if path is within allowed boundaries
88
- """
89
- try:
90
- if not file_path:
91
- log_warning("Empty file path provided to boundary check")
92
- return False
93
-
94
- # Resolve real path to handle symlinks
95
- real_path = os.path.realpath(file_path)
96
-
97
- # Check against all allowed directories
98
- for allowed_dir in self.allowed_directories:
99
- if (
100
- real_path.startswith(allowed_dir + os.sep)
101
- or real_path == allowed_dir
102
- ):
103
- log_debug(f"File path within boundaries: {file_path}")
104
- return True
105
-
106
- log_warning(f"File path outside boundaries: {file_path} -> {real_path}")
107
- return False
108
-
109
- except Exception as e:
110
- log_warning(f"Boundary check error for {file_path}: {e}")
111
- return False
112
-
113
- def get_relative_path(self, file_path: str) -> str | None:
114
- """
115
- Get relative path from project root if within boundaries.
116
-
117
- Args:
118
- file_path: File path to convert
119
-
120
- Returns:
121
- Relative path from project root, or None if outside boundaries
122
- """
123
- if not self.is_within_project(file_path):
124
- return None
125
-
126
- try:
127
- real_path = os.path.realpath(file_path)
128
- rel_path = os.path.relpath(real_path, self.project_root)
129
-
130
- # Ensure relative path doesn't start with ..
131
- if rel_path.startswith(".."):
132
- log_warning(f"Relative path calculation failed: {rel_path}")
133
- return None
134
-
135
- return rel_path
136
-
137
- except Exception as e:
138
- log_warning(f"Relative path calculation error: {e}")
139
- return None
140
-
141
- def validate_and_resolve_path(self, file_path: str) -> str | None:
142
- """
143
- Validate path and return resolved absolute path if within boundaries.
144
-
145
- Args:
146
- file_path: File path to validate and resolve
147
-
148
- Returns:
149
- Resolved absolute path if valid, None otherwise
150
- """
151
- try:
152
- # Handle relative paths from project root
153
- if not os.path.isabs(file_path):
154
- full_path = os.path.join(self.project_root, file_path)
155
- else:
156
- full_path = file_path
157
-
158
- # Check boundaries
159
- if not self.is_within_project(full_path):
160
- return None
161
-
162
- # Return real path
163
- return os.path.realpath(full_path)
164
-
165
- except Exception as e:
166
- log_warning(f"Path validation error: {e}")
167
- return None
168
-
169
- def list_allowed_directories(self) -> set[str]:
170
- """
171
- Get list of all allowed directories.
172
-
173
- Returns:
174
- Set of allowed directory paths
175
- """
176
- return self.allowed_directories.copy()
177
-
178
- def is_symlink_safe(self, file_path: str) -> bool:
179
- """
180
- Check if file path is safe from symlink attacks.
181
-
182
- Args:
183
- file_path: File path to check
184
-
185
- Returns:
186
- True if path is safe from symlink attacks
187
- """
188
- try:
189
- if not os.path.exists(file_path):
190
- return True # Non-existent files are safe
191
-
192
- # Check if any component in the path is a symlink
193
- path_parts = Path(file_path).parts
194
- current_path = ""
195
-
196
- for part in path_parts:
197
- current_path = (
198
- os.path.join(current_path, part) if current_path else part
199
- )
200
-
201
- if os.path.islink(current_path):
202
- # Check if symlink target is within boundaries
203
- target = os.path.realpath(current_path)
204
- if not self.is_within_project(target):
205
- log_warning(
206
- f"Unsafe symlink detected: {current_path} -> {target}"
207
- )
208
- return False
209
-
210
- return True
211
-
212
- except Exception as e:
213
- log_warning(f"Symlink safety check error: {e}")
214
- return False
215
-
216
- def audit_access(self, file_path: str, operation: str) -> None:
217
- """
218
- Log file access for security auditing.
219
-
220
- Args:
221
- file_path: File path being accessed
222
- operation: Type of operation (read, write, analyze, etc.)
223
- """
224
- is_within = self.is_within_project(file_path)
225
- status = "ALLOWED" if is_within else "DENIED"
226
-
227
- log_info(f"AUDIT: {status} {operation} access to {file_path}")
228
-
229
- if not is_within:
230
- log_warning(f"SECURITY: Unauthorized access attempt to {file_path}")
231
-
232
- def __str__(self) -> str:
233
- """String representation of boundary manager."""
234
- return f"ProjectBoundaryManager(root={self.project_root}, allowed_dirs={len(self.allowed_directories)})"
235
-
236
- def __repr__(self) -> str:
237
- """Detailed representation of boundary manager."""
238
- return (
239
- f"ProjectBoundaryManager("
240
- f"project_root='{self.project_root}', "
241
- f"allowed_directories={self.allowed_directories}"
242
- f")"
243
- )
1
+ #!/usr/bin/env python3
2
+ """
3
+ Project Boundary Manager for Tree-sitter Analyzer
4
+
5
+ Provides strict project boundary control to prevent access to files
6
+ outside the designated project directory.
7
+ """
8
+
9
+ import os
10
+ from pathlib import Path
11
+
12
+ from ..exceptions import SecurityError
13
+ from ..utils import log_debug, log_info, log_warning
14
+
15
+
16
+ class ProjectBoundaryManager:
17
+ """
18
+ Project boundary manager for access control.
19
+
20
+ This class enforces strict boundaries around project directories
21
+ to prevent unauthorized file access outside the project scope.
22
+
23
+ Features:
24
+ - Real path resolution for symlink protection
25
+ - Configurable allowed directories
26
+ - Comprehensive boundary checking
27
+ - Audit logging for security events
28
+ """
29
+
30
+ def __init__(self, project_root: str) -> None:
31
+ """
32
+ Initialize project boundary manager.
33
+
34
+ Args:
35
+ project_root: Root directory of the project
36
+
37
+ Raises:
38
+ SecurityError: If project root is invalid
39
+ """
40
+ if not project_root:
41
+ raise SecurityError("Project root cannot be empty")
42
+
43
+ if not os.path.exists(project_root):
44
+ raise SecurityError(f"Project root does not exist: {project_root}")
45
+
46
+ if not os.path.isdir(project_root):
47
+ raise SecurityError(f"Project root is not a directory: {project_root}")
48
+
49
+ # Store real path to prevent symlink attacks
50
+ self.project_root = os.path.realpath(project_root)
51
+ self.allowed_directories: set[str] = {self.project_root}
52
+
53
+ log_debug(f"ProjectBoundaryManager initialized with root: {self.project_root}")
54
+
55
+ def add_allowed_directory(self, directory: str) -> None:
56
+ """
57
+ Add an additional allowed directory.
58
+
59
+ Args:
60
+ directory: Directory path to allow access to
61
+
62
+ Raises:
63
+ SecurityError: If directory is invalid
64
+ """
65
+ if not directory:
66
+ raise SecurityError("Directory cannot be empty")
67
+
68
+ if not os.path.exists(directory):
69
+ raise SecurityError(f"Directory does not exist: {directory}")
70
+
71
+ if not os.path.isdir(directory):
72
+ raise SecurityError(f"Path is not a directory: {directory}")
73
+
74
+ real_dir = os.path.realpath(directory)
75
+ self.allowed_directories.add(real_dir)
76
+
77
+ log_info(f"Added allowed directory: {real_dir}")
78
+
79
+ def is_within_project(self, file_path: str) -> bool:
80
+ """
81
+ Check if file path is within project boundaries.
82
+
83
+ Args:
84
+ file_path: File path to check
85
+
86
+ Returns:
87
+ True if path is within allowed boundaries
88
+ """
89
+ try:
90
+ if not file_path:
91
+ log_warning("Empty file path provided to boundary check")
92
+ return False
93
+
94
+ # Resolve real path to handle symlinks
95
+ real_path = os.path.realpath(file_path)
96
+
97
+ # Check against all allowed directories
98
+ for allowed_dir in self.allowed_directories:
99
+ if (
100
+ real_path.startswith(allowed_dir + os.sep)
101
+ or real_path == allowed_dir
102
+ ):
103
+ log_debug(f"File path within boundaries: {file_path}")
104
+ return True
105
+
106
+ log_warning(f"File path outside boundaries: {file_path} -> {real_path}")
107
+ return False
108
+
109
+ except Exception as e:
110
+ log_warning(f"Boundary check error for {file_path}: {e}")
111
+ return False
112
+
113
+ def get_relative_path(self, file_path: str) -> str | None:
114
+ """
115
+ Get relative path from project root if within boundaries.
116
+
117
+ Args:
118
+ file_path: File path to convert
119
+
120
+ Returns:
121
+ Relative path from project root, or None if outside boundaries
122
+ """
123
+ if not self.is_within_project(file_path):
124
+ return None
125
+
126
+ try:
127
+ real_path = os.path.realpath(file_path)
128
+ rel_path = os.path.relpath(real_path, self.project_root)
129
+
130
+ # Ensure relative path doesn't start with ..
131
+ if rel_path.startswith(".."):
132
+ log_warning(f"Relative path calculation failed: {rel_path}")
133
+ return None
134
+
135
+ return rel_path
136
+
137
+ except Exception as e:
138
+ log_warning(f"Relative path calculation error: {e}")
139
+ return None
140
+
141
+ def validate_and_resolve_path(self, file_path: str) -> str | None:
142
+ """
143
+ Validate path and return resolved absolute path if within boundaries.
144
+
145
+ Args:
146
+ file_path: File path to validate and resolve
147
+
148
+ Returns:
149
+ Resolved absolute path if valid, None otherwise
150
+ """
151
+ try:
152
+ # Handle relative paths from project root
153
+ if not os.path.isabs(file_path):
154
+ full_path = os.path.join(self.project_root, file_path)
155
+ else:
156
+ full_path = file_path
157
+
158
+ # Check boundaries
159
+ if not self.is_within_project(full_path):
160
+ return None
161
+
162
+ # Return real path
163
+ return os.path.realpath(full_path)
164
+
165
+ except Exception as e:
166
+ log_warning(f"Path validation error: {e}")
167
+ return None
168
+
169
+ def list_allowed_directories(self) -> set[str]:
170
+ """
171
+ Get list of all allowed directories.
172
+
173
+ Returns:
174
+ Set of allowed directory paths
175
+ """
176
+ return self.allowed_directories.copy()
177
+
178
+ def is_symlink_safe(self, file_path: str) -> bool:
179
+ """
180
+ Check if file path is safe from symlink attacks.
181
+
182
+ Args:
183
+ file_path: File path to check
184
+
185
+ Returns:
186
+ True if path is safe from symlink attacks
187
+ """
188
+ try:
189
+ if not os.path.exists(file_path):
190
+ return True # Non-existent files are safe
191
+
192
+ # If the fully resolved path is within project boundaries, we treat it as safe.
193
+ # This makes the check tolerant to system-level symlinks like
194
+ # /var -> /private/var on macOS runners.
195
+ resolved = os.path.realpath(file_path)
196
+ if self.is_within_project(resolved):
197
+ return True
198
+
199
+ # Otherwise, inspect each path component symlink to ensure no hop jumps outside
200
+ # the allowed directories.
201
+ path_parts = Path(file_path).parts
202
+ current_path = ""
203
+
204
+ for part in path_parts:
205
+ current_path = (
206
+ os.path.join(current_path, part) if current_path else part
207
+ )
208
+
209
+ if os.path.islink(current_path):
210
+ target = os.path.realpath(current_path)
211
+ if not self.is_within_project(target):
212
+ log_warning(
213
+ f"Unsafe symlink detected: {current_path} -> {target}"
214
+ )
215
+ return False
216
+
217
+ # If no unsafe hop found, consider safe
218
+ return True
219
+
220
+ except Exception as e:
221
+ log_warning(f"Symlink safety check error: {e}")
222
+ return False
223
+
224
+ def audit_access(self, file_path: str, operation: str) -> None:
225
+ """
226
+ Log file access for security auditing.
227
+
228
+ Args:
229
+ file_path: File path being accessed
230
+ operation: Type of operation (read, write, analyze, etc.)
231
+ """
232
+ is_within = self.is_within_project(file_path)
233
+ status = "ALLOWED" if is_within else "DENIED"
234
+
235
+ log_info(f"AUDIT: {status} {operation} access to {file_path}")
236
+
237
+ if not is_within:
238
+ log_warning(f"SECURITY: Unauthorized access attempt to {file_path}")
239
+
240
+ def __str__(self) -> str:
241
+ """String representation of boundary manager."""
242
+ return f"ProjectBoundaryManager(root={self.project_root}, allowed_dirs={len(self.allowed_directories)})"
243
+
244
+ def __repr__(self) -> str:
245
+ """Detailed representation of boundary manager."""
246
+ return (
247
+ f"ProjectBoundaryManager("
248
+ f"project_root='{self.project_root}', "
249
+ f"allowed_directories={self.allowed_directories}"
250
+ f")"
251
+ )