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

@@ -1,237 +1,237 @@
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
- from typing import Optional, Set
12
-
13
- from ..exceptions import SecurityError
14
- from ..utils import log_debug, log_info, log_warning
15
-
16
-
17
- class ProjectBoundaryManager:
18
- """
19
- Project boundary manager for access control.
20
-
21
- This class enforces strict boundaries around project directories
22
- to prevent unauthorized file access outside the project scope.
23
-
24
- Features:
25
- - Real path resolution for symlink protection
26
- - Configurable allowed directories
27
- - Comprehensive boundary checking
28
- - Audit logging for security events
29
- """
30
-
31
- def __init__(self, project_root: str) -> None:
32
- """
33
- Initialize project boundary manager.
34
-
35
- Args:
36
- project_root: Root directory of the project
37
-
38
- Raises:
39
- SecurityError: If project root is invalid
40
- """
41
- if not project_root:
42
- raise SecurityError("Project root cannot be empty")
43
-
44
- if not os.path.exists(project_root):
45
- raise SecurityError(f"Project root does not exist: {project_root}")
46
-
47
- if not os.path.isdir(project_root):
48
- raise SecurityError(f"Project root is not a directory: {project_root}")
49
-
50
- # Store real path to prevent symlink attacks
51
- self.project_root = os.path.realpath(project_root)
52
- self.allowed_directories: Set[str] = {self.project_root}
53
-
54
- log_info(f"ProjectBoundaryManager initialized with root: {self.project_root}")
55
-
56
- def add_allowed_directory(self, directory: str) -> None:
57
- """
58
- Add an additional allowed directory.
59
-
60
- Args:
61
- directory: Directory path to allow access to
62
-
63
- Raises:
64
- SecurityError: If directory is invalid
65
- """
66
- if not directory:
67
- raise SecurityError("Directory cannot be empty")
68
-
69
- if not os.path.exists(directory):
70
- raise SecurityError(f"Directory does not exist: {directory}")
71
-
72
- if not os.path.isdir(directory):
73
- raise SecurityError(f"Path is not a directory: {directory}")
74
-
75
- real_dir = os.path.realpath(directory)
76
- self.allowed_directories.add(real_dir)
77
-
78
- log_info(f"Added allowed directory: {real_dir}")
79
-
80
- def is_within_project(self, file_path: str) -> bool:
81
- """
82
- Check if file path is within project boundaries.
83
-
84
- Args:
85
- file_path: File path to check
86
-
87
- Returns:
88
- True if path is within allowed boundaries
89
- """
90
- try:
91
- if not file_path:
92
- log_warning("Empty file path provided to boundary check")
93
- return False
94
-
95
- # Resolve real path to handle symlinks
96
- real_path = os.path.realpath(file_path)
97
-
98
- # Check against all allowed directories
99
- for allowed_dir in self.allowed_directories:
100
- if real_path.startswith(allowed_dir + os.sep) or real_path == allowed_dir:
101
- log_debug(f"File path within boundaries: {file_path}")
102
- return True
103
-
104
- log_warning(f"File path outside boundaries: {file_path} -> {real_path}")
105
- return False
106
-
107
- except Exception as e:
108
- log_warning(f"Boundary check error for {file_path}: {e}")
109
- return False
110
-
111
- def get_relative_path(self, file_path: str) -> Optional[str]:
112
- """
113
- Get relative path from project root if within boundaries.
114
-
115
- Args:
116
- file_path: File path to convert
117
-
118
- Returns:
119
- Relative path from project root, or None if outside boundaries
120
- """
121
- if not self.is_within_project(file_path):
122
- return None
123
-
124
- try:
125
- real_path = os.path.realpath(file_path)
126
- rel_path = os.path.relpath(real_path, self.project_root)
127
-
128
- # Ensure relative path doesn't start with ..
129
- if rel_path.startswith(".."):
130
- log_warning(f"Relative path calculation failed: {rel_path}")
131
- return None
132
-
133
- return rel_path
134
-
135
- except Exception as e:
136
- log_warning(f"Relative path calculation error: {e}")
137
- return None
138
-
139
- def validate_and_resolve_path(self, file_path: str) -> Optional[str]:
140
- """
141
- Validate path and return resolved absolute path if within boundaries.
142
-
143
- Args:
144
- file_path: File path to validate and resolve
145
-
146
- Returns:
147
- Resolved absolute path if valid, None otherwise
148
- """
149
- try:
150
- # Handle relative paths from project root
151
- if not os.path.isabs(file_path):
152
- full_path = os.path.join(self.project_root, file_path)
153
- else:
154
- full_path = file_path
155
-
156
- # Check boundaries
157
- if not self.is_within_project(full_path):
158
- return None
159
-
160
- # Return real path
161
- return os.path.realpath(full_path)
162
-
163
- except Exception as e:
164
- log_warning(f"Path validation error: {e}")
165
- return None
166
-
167
- def list_allowed_directories(self) -> Set[str]:
168
- """
169
- Get list of all allowed directories.
170
-
171
- Returns:
172
- Set of allowed directory paths
173
- """
174
- return self.allowed_directories.copy()
175
-
176
- def is_symlink_safe(self, file_path: str) -> bool:
177
- """
178
- Check if file path is safe from symlink attacks.
179
-
180
- Args:
181
- file_path: File path to check
182
-
183
- Returns:
184
- True if path is safe from symlink attacks
185
- """
186
- try:
187
- if not os.path.exists(file_path):
188
- return True # Non-existent files are safe
189
-
190
- # Check if any component in the path is a symlink
191
- path_parts = Path(file_path).parts
192
- current_path = ""
193
-
194
- for part in path_parts:
195
- current_path = os.path.join(current_path, part) if current_path else part
196
-
197
- if os.path.islink(current_path):
198
- # Check if symlink target is within boundaries
199
- target = os.path.realpath(current_path)
200
- if not self.is_within_project(target):
201
- log_warning(f"Unsafe symlink detected: {current_path} -> {target}")
202
- return False
203
-
204
- return True
205
-
206
- except Exception as e:
207
- log_warning(f"Symlink safety check error: {e}")
208
- return False
209
-
210
- def audit_access(self, file_path: str, operation: str) -> None:
211
- """
212
- Log file access for security auditing.
213
-
214
- Args:
215
- file_path: File path being accessed
216
- operation: Type of operation (read, write, analyze, etc.)
217
- """
218
- is_within = self.is_within_project(file_path)
219
- status = "ALLOWED" if is_within else "DENIED"
220
-
221
- log_info(f"AUDIT: {status} {operation} access to {file_path}")
222
-
223
- if not is_within:
224
- log_warning(f"SECURITY: Unauthorized access attempt to {file_path}")
225
-
226
- def __str__(self) -> str:
227
- """String representation of boundary manager."""
228
- return f"ProjectBoundaryManager(root={self.project_root}, allowed_dirs={len(self.allowed_directories)})"
229
-
230
- def __repr__(self) -> str:
231
- """Detailed representation of boundary manager."""
232
- return (
233
- f"ProjectBoundaryManager("
234
- f"project_root='{self.project_root}', "
235
- f"allowed_directories={self.allowed_directories}"
236
- f")"
237
- )
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
+ from typing import Optional, Set
12
+
13
+ from ..exceptions import SecurityError
14
+ from ..utils import log_debug, log_info, log_warning
15
+
16
+
17
+ class ProjectBoundaryManager:
18
+ """
19
+ Project boundary manager for access control.
20
+
21
+ This class enforces strict boundaries around project directories
22
+ to prevent unauthorized file access outside the project scope.
23
+
24
+ Features:
25
+ - Real path resolution for symlink protection
26
+ - Configurable allowed directories
27
+ - Comprehensive boundary checking
28
+ - Audit logging for security events
29
+ """
30
+
31
+ def __init__(self, project_root: str) -> None:
32
+ """
33
+ Initialize project boundary manager.
34
+
35
+ Args:
36
+ project_root: Root directory of the project
37
+
38
+ Raises:
39
+ SecurityError: If project root is invalid
40
+ """
41
+ if not project_root:
42
+ raise SecurityError("Project root cannot be empty")
43
+
44
+ if not os.path.exists(project_root):
45
+ raise SecurityError(f"Project root does not exist: {project_root}")
46
+
47
+ if not os.path.isdir(project_root):
48
+ raise SecurityError(f"Project root is not a directory: {project_root}")
49
+
50
+ # Store real path to prevent symlink attacks
51
+ self.project_root = os.path.realpath(project_root)
52
+ self.allowed_directories: Set[str] = {self.project_root}
53
+
54
+ log_info(f"ProjectBoundaryManager initialized with root: {self.project_root}")
55
+
56
+ def add_allowed_directory(self, directory: str) -> None:
57
+ """
58
+ Add an additional allowed directory.
59
+
60
+ Args:
61
+ directory: Directory path to allow access to
62
+
63
+ Raises:
64
+ SecurityError: If directory is invalid
65
+ """
66
+ if not directory:
67
+ raise SecurityError("Directory cannot be empty")
68
+
69
+ if not os.path.exists(directory):
70
+ raise SecurityError(f"Directory does not exist: {directory}")
71
+
72
+ if not os.path.isdir(directory):
73
+ raise SecurityError(f"Path is not a directory: {directory}")
74
+
75
+ real_dir = os.path.realpath(directory)
76
+ self.allowed_directories.add(real_dir)
77
+
78
+ log_info(f"Added allowed directory: {real_dir}")
79
+
80
+ def is_within_project(self, file_path: str) -> bool:
81
+ """
82
+ Check if file path is within project boundaries.
83
+
84
+ Args:
85
+ file_path: File path to check
86
+
87
+ Returns:
88
+ True if path is within allowed boundaries
89
+ """
90
+ try:
91
+ if not file_path:
92
+ log_warning("Empty file path provided to boundary check")
93
+ return False
94
+
95
+ # Resolve real path to handle symlinks
96
+ real_path = os.path.realpath(file_path)
97
+
98
+ # Check against all allowed directories
99
+ for allowed_dir in self.allowed_directories:
100
+ if real_path.startswith(allowed_dir + os.sep) or real_path == allowed_dir:
101
+ log_debug(f"File path within boundaries: {file_path}")
102
+ return True
103
+
104
+ log_warning(f"File path outside boundaries: {file_path} -> {real_path}")
105
+ return False
106
+
107
+ except Exception as e:
108
+ log_warning(f"Boundary check error for {file_path}: {e}")
109
+ return False
110
+
111
+ def get_relative_path(self, file_path: str) -> Optional[str]:
112
+ """
113
+ Get relative path from project root if within boundaries.
114
+
115
+ Args:
116
+ file_path: File path to convert
117
+
118
+ Returns:
119
+ Relative path from project root, or None if outside boundaries
120
+ """
121
+ if not self.is_within_project(file_path):
122
+ return None
123
+
124
+ try:
125
+ real_path = os.path.realpath(file_path)
126
+ rel_path = os.path.relpath(real_path, self.project_root)
127
+
128
+ # Ensure relative path doesn't start with ..
129
+ if rel_path.startswith(".."):
130
+ log_warning(f"Relative path calculation failed: {rel_path}")
131
+ return None
132
+
133
+ return rel_path
134
+
135
+ except Exception as e:
136
+ log_warning(f"Relative path calculation error: {e}")
137
+ return None
138
+
139
+ def validate_and_resolve_path(self, file_path: str) -> Optional[str]:
140
+ """
141
+ Validate path and return resolved absolute path if within boundaries.
142
+
143
+ Args:
144
+ file_path: File path to validate and resolve
145
+
146
+ Returns:
147
+ Resolved absolute path if valid, None otherwise
148
+ """
149
+ try:
150
+ # Handle relative paths from project root
151
+ if not os.path.isabs(file_path):
152
+ full_path = os.path.join(self.project_root, file_path)
153
+ else:
154
+ full_path = file_path
155
+
156
+ # Check boundaries
157
+ if not self.is_within_project(full_path):
158
+ return None
159
+
160
+ # Return real path
161
+ return os.path.realpath(full_path)
162
+
163
+ except Exception as e:
164
+ log_warning(f"Path validation error: {e}")
165
+ return None
166
+
167
+ def list_allowed_directories(self) -> Set[str]:
168
+ """
169
+ Get list of all allowed directories.
170
+
171
+ Returns:
172
+ Set of allowed directory paths
173
+ """
174
+ return self.allowed_directories.copy()
175
+
176
+ def is_symlink_safe(self, file_path: str) -> bool:
177
+ """
178
+ Check if file path is safe from symlink attacks.
179
+
180
+ Args:
181
+ file_path: File path to check
182
+
183
+ Returns:
184
+ True if path is safe from symlink attacks
185
+ """
186
+ try:
187
+ if not os.path.exists(file_path):
188
+ return True # Non-existent files are safe
189
+
190
+ # Check if any component in the path is a symlink
191
+ path_parts = Path(file_path).parts
192
+ current_path = ""
193
+
194
+ for part in path_parts:
195
+ current_path = os.path.join(current_path, part) if current_path else part
196
+
197
+ if os.path.islink(current_path):
198
+ # Check if symlink target is within boundaries
199
+ target = os.path.realpath(current_path)
200
+ if not self.is_within_project(target):
201
+ log_warning(f"Unsafe symlink detected: {current_path} -> {target}")
202
+ return False
203
+
204
+ return True
205
+
206
+ except Exception as e:
207
+ log_warning(f"Symlink safety check error: {e}")
208
+ return False
209
+
210
+ def audit_access(self, file_path: str, operation: str) -> None:
211
+ """
212
+ Log file access for security auditing.
213
+
214
+ Args:
215
+ file_path: File path being accessed
216
+ operation: Type of operation (read, write, analyze, etc.)
217
+ """
218
+ is_within = self.is_within_project(file_path)
219
+ status = "ALLOWED" if is_within else "DENIED"
220
+
221
+ log_info(f"AUDIT: {status} {operation} access to {file_path}")
222
+
223
+ if not is_within:
224
+ log_warning(f"SECURITY: Unauthorized access attempt to {file_path}")
225
+
226
+ def __str__(self) -> str:
227
+ """String representation of boundary manager."""
228
+ return f"ProjectBoundaryManager(root={self.project_root}, allowed_dirs={len(self.allowed_directories)})"
229
+
230
+ def __repr__(self) -> str:
231
+ """Detailed representation of boundary manager."""
232
+ return (
233
+ f"ProjectBoundaryManager("
234
+ f"project_root='{self.project_root}', "
235
+ f"allowed_directories={self.allowed_directories}"
236
+ f")"
237
+ )