onetool-mcp 1.0.0b1__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.
- bench/__init__.py +5 -0
- bench/cli.py +69 -0
- bench/harness/__init__.py +66 -0
- bench/harness/client.py +692 -0
- bench/harness/config.py +397 -0
- bench/harness/csv_writer.py +109 -0
- bench/harness/evaluate.py +512 -0
- bench/harness/metrics.py +283 -0
- bench/harness/runner.py +899 -0
- bench/py.typed +0 -0
- bench/reporter.py +629 -0
- bench/run.py +487 -0
- bench/secrets.py +101 -0
- bench/utils.py +16 -0
- onetool/__init__.py +4 -0
- onetool/cli.py +391 -0
- onetool/py.typed +0 -0
- onetool_mcp-1.0.0b1.dist-info/METADATA +163 -0
- onetool_mcp-1.0.0b1.dist-info/RECORD +132 -0
- onetool_mcp-1.0.0b1.dist-info/WHEEL +4 -0
- onetool_mcp-1.0.0b1.dist-info/entry_points.txt +3 -0
- onetool_mcp-1.0.0b1.dist-info/licenses/LICENSE.txt +687 -0
- onetool_mcp-1.0.0b1.dist-info/licenses/NOTICE.txt +64 -0
- ot/__init__.py +37 -0
- ot/__main__.py +6 -0
- ot/_cli.py +107 -0
- ot/_tui.py +53 -0
- ot/config/__init__.py +46 -0
- ot/config/defaults/bench.yaml +4 -0
- ot/config/defaults/diagram-templates/api-flow.mmd +33 -0
- ot/config/defaults/diagram-templates/c4-context.puml +30 -0
- ot/config/defaults/diagram-templates/class-diagram.mmd +87 -0
- ot/config/defaults/diagram-templates/feature-mindmap.mmd +70 -0
- ot/config/defaults/diagram-templates/microservices.d2 +81 -0
- ot/config/defaults/diagram-templates/project-gantt.mmd +37 -0
- ot/config/defaults/diagram-templates/state-machine.mmd +42 -0
- ot/config/defaults/onetool.yaml +25 -0
- ot/config/defaults/prompts.yaml +97 -0
- ot/config/defaults/servers.yaml +7 -0
- ot/config/defaults/snippets.yaml +4 -0
- ot/config/defaults/tool_templates/__init__.py +7 -0
- ot/config/defaults/tool_templates/extension.py +52 -0
- ot/config/defaults/tool_templates/isolated.py +61 -0
- ot/config/dynamic.py +121 -0
- ot/config/global_templates/__init__.py +2 -0
- ot/config/global_templates/bench-secrets-template.yaml +6 -0
- ot/config/global_templates/bench.yaml +9 -0
- ot/config/global_templates/onetool.yaml +27 -0
- ot/config/global_templates/secrets-template.yaml +44 -0
- ot/config/global_templates/servers.yaml +18 -0
- ot/config/global_templates/snippets.yaml +235 -0
- ot/config/loader.py +1087 -0
- ot/config/mcp.py +145 -0
- ot/config/secrets.py +190 -0
- ot/config/tool_config.py +125 -0
- ot/decorators.py +116 -0
- ot/executor/__init__.py +35 -0
- ot/executor/base.py +16 -0
- ot/executor/fence_processor.py +83 -0
- ot/executor/linter.py +142 -0
- ot/executor/pack_proxy.py +260 -0
- ot/executor/param_resolver.py +140 -0
- ot/executor/pep723.py +288 -0
- ot/executor/result_store.py +369 -0
- ot/executor/runner.py +496 -0
- ot/executor/simple.py +163 -0
- ot/executor/tool_loader.py +396 -0
- ot/executor/validator.py +398 -0
- ot/executor/worker_pool.py +388 -0
- ot/executor/worker_proxy.py +189 -0
- ot/http_client.py +145 -0
- ot/logging/__init__.py +37 -0
- ot/logging/config.py +315 -0
- ot/logging/entry.py +213 -0
- ot/logging/format.py +188 -0
- ot/logging/span.py +349 -0
- ot/meta.py +1555 -0
- ot/paths.py +453 -0
- ot/prompts.py +218 -0
- ot/proxy/__init__.py +21 -0
- ot/proxy/manager.py +396 -0
- ot/py.typed +0 -0
- ot/registry/__init__.py +189 -0
- ot/registry/models.py +57 -0
- ot/registry/parser.py +269 -0
- ot/registry/registry.py +413 -0
- ot/server.py +315 -0
- ot/shortcuts/__init__.py +15 -0
- ot/shortcuts/aliases.py +87 -0
- ot/shortcuts/snippets.py +258 -0
- ot/stats/__init__.py +35 -0
- ot/stats/html.py +250 -0
- ot/stats/jsonl_writer.py +283 -0
- ot/stats/reader.py +354 -0
- ot/stats/timing.py +57 -0
- ot/support.py +63 -0
- ot/tools.py +114 -0
- ot/utils/__init__.py +81 -0
- ot/utils/batch.py +161 -0
- ot/utils/cache.py +120 -0
- ot/utils/deps.py +403 -0
- ot/utils/exceptions.py +23 -0
- ot/utils/factory.py +179 -0
- ot/utils/format.py +65 -0
- ot/utils/http.py +202 -0
- ot/utils/platform.py +45 -0
- ot/utils/sanitize.py +130 -0
- ot/utils/truncate.py +69 -0
- ot_tools/__init__.py +4 -0
- ot_tools/_convert/__init__.py +12 -0
- ot_tools/_convert/excel.py +279 -0
- ot_tools/_convert/pdf.py +254 -0
- ot_tools/_convert/powerpoint.py +268 -0
- ot_tools/_convert/utils.py +358 -0
- ot_tools/_convert/word.py +283 -0
- ot_tools/brave_search.py +604 -0
- ot_tools/code_search.py +736 -0
- ot_tools/context7.py +495 -0
- ot_tools/convert.py +614 -0
- ot_tools/db.py +415 -0
- ot_tools/diagram.py +1604 -0
- ot_tools/diagram.yaml +167 -0
- ot_tools/excel.py +1372 -0
- ot_tools/file.py +1348 -0
- ot_tools/firecrawl.py +732 -0
- ot_tools/grounding_search.py +646 -0
- ot_tools/package.py +604 -0
- ot_tools/py.typed +0 -0
- ot_tools/ripgrep.py +544 -0
- ot_tools/scaffold.py +471 -0
- ot_tools/transform.py +213 -0
- ot_tools/web_fetch.py +384 -0
ot/executor/validator.py
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"""AST-based code validation for OneTool.
|
|
2
|
+
|
|
3
|
+
Validates Python code before execution:
|
|
4
|
+
- Syntax validation via ast.parse()
|
|
5
|
+
- Security pattern detection (dangerous calls)
|
|
6
|
+
- Optional Ruff linting integration for style warnings
|
|
7
|
+
|
|
8
|
+
Security patterns are configurable via onetool.yaml and support wildcards:
|
|
9
|
+
|
|
10
|
+
security:
|
|
11
|
+
validate_code: true
|
|
12
|
+
enabled: true
|
|
13
|
+
blocked: [exec, eval, compile, __import__, subprocess.*, os.system]
|
|
14
|
+
warned: [subprocess, os, open, pickle.*, yaml.load]
|
|
15
|
+
|
|
16
|
+
Pattern matching logic (automatic based on pattern structure):
|
|
17
|
+
- Patterns WITHOUT dots (e.g., 'exec', 'subprocess') match:
|
|
18
|
+
* Builtin function calls: exec(), eval()
|
|
19
|
+
* Import statements: import subprocess, from os import system
|
|
20
|
+
- Patterns WITH dots (e.g., 'subprocess.*', 'os.system') match:
|
|
21
|
+
* Qualified function calls: subprocess.run(), os.system()
|
|
22
|
+
|
|
23
|
+
Wildcard patterns use fnmatch syntax:
|
|
24
|
+
- '*' matches any characters (e.g., 'subprocess.*' matches 'subprocess.run')
|
|
25
|
+
- '?' matches a single character
|
|
26
|
+
- '[seq]' matches any character in seq
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
result = validate_python_code(code)
|
|
30
|
+
if not result.valid:
|
|
31
|
+
print(f"Validation errors: {result.errors}")
|
|
32
|
+
if result.warnings:
|
|
33
|
+
print(f"Warnings: {result.warnings}")
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import ast
|
|
39
|
+
import fnmatch
|
|
40
|
+
from dataclasses import dataclass, field
|
|
41
|
+
from typing import TYPE_CHECKING
|
|
42
|
+
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
from ot.config.loader import SecurityConfig
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class ValidationResult:
|
|
49
|
+
"""Result of code validation."""
|
|
50
|
+
|
|
51
|
+
valid: bool = True
|
|
52
|
+
errors: list[str] = field(default_factory=list)
|
|
53
|
+
warnings: list[str] = field(default_factory=list)
|
|
54
|
+
ast_tree: ast.Module | None = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# =============================================================================
|
|
58
|
+
# Default Security Patterns
|
|
59
|
+
# =============================================================================
|
|
60
|
+
# These are used when config is not available. Patterns use fnmatch wildcards.
|
|
61
|
+
#
|
|
62
|
+
# Pattern matching is determined by structure:
|
|
63
|
+
# - No dot in pattern → matches builtins (calls) AND imports
|
|
64
|
+
# - Dot in pattern → matches qualified function calls only
|
|
65
|
+
#
|
|
66
|
+
# This two-category design (blocked/warned) replaces the previous four-category
|
|
67
|
+
# design (blocked_builtins, blocked_functions, warned_functions, warned_imports).
|
|
68
|
+
# The validator automatically determines the match type based on pattern structure.
|
|
69
|
+
# =============================================================================
|
|
70
|
+
|
|
71
|
+
DEFAULT_BLOCKED = frozenset(
|
|
72
|
+
{
|
|
73
|
+
# Builtins - arbitrary code execution risk (no dots = match calls + imports)
|
|
74
|
+
"exec",
|
|
75
|
+
"eval",
|
|
76
|
+
"compile",
|
|
77
|
+
"__import__",
|
|
78
|
+
# Qualified functions - command injection (dots = match calls only)
|
|
79
|
+
"subprocess.*", # All subprocess functions
|
|
80
|
+
"os.system", # Shell command execution
|
|
81
|
+
"os.popen", # Shell command execution
|
|
82
|
+
"os.spawn*", # spawnl, spawnle, spawnv, etc.
|
|
83
|
+
"os.exec*", # execl, execle, execv, etc.
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
DEFAULT_WARNED = frozenset(
|
|
88
|
+
{
|
|
89
|
+
# Module imports - enables dangerous operations (no dots = match imports)
|
|
90
|
+
"subprocess",
|
|
91
|
+
"os",
|
|
92
|
+
# Qualified functions - file access, deserialization (dots = match calls)
|
|
93
|
+
"open", # File access (common but risky)
|
|
94
|
+
"pickle.*", # Deserialization attacks
|
|
95
|
+
"yaml.load", # Unsafe YAML deserialization
|
|
96
|
+
"marshal.*", # Object deserialization
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _matches_pattern(name: str, patterns: frozenset[str]) -> str | None:
|
|
102
|
+
"""Check if a name matches any pattern in the set.
|
|
103
|
+
|
|
104
|
+
Supports both exact matches and fnmatch wildcards.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
name: The function/module name to check
|
|
108
|
+
patterns: Set of patterns (may contain wildcards)
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
The matching pattern if found, None otherwise
|
|
112
|
+
"""
|
|
113
|
+
# Fast path: exact match
|
|
114
|
+
if name in patterns:
|
|
115
|
+
return name
|
|
116
|
+
|
|
117
|
+
# Check wildcard patterns
|
|
118
|
+
for pattern in patterns:
|
|
119
|
+
if ("*" in pattern or "?" in pattern or "[" in pattern) and fnmatch.fnmatch(
|
|
120
|
+
name, pattern
|
|
121
|
+
):
|
|
122
|
+
return pattern
|
|
123
|
+
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _has_dot(pattern: str) -> bool:
|
|
128
|
+
"""Check if a pattern contains a dot (qualified name).
|
|
129
|
+
|
|
130
|
+
Used to determine matching strategy:
|
|
131
|
+
- No dot: match builtins and imports
|
|
132
|
+
- Has dot: match qualified function calls
|
|
133
|
+
"""
|
|
134
|
+
return "." in pattern
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _get_security_config() -> SecurityConfig | None:
|
|
138
|
+
"""Get security configuration from global config.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
SecurityConfig if available, None otherwise.
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
from ot.config.loader import get_config
|
|
145
|
+
|
|
146
|
+
config = get_config()
|
|
147
|
+
return config.security
|
|
148
|
+
except Exception:
|
|
149
|
+
# Config not loaded yet or error - use defaults
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class DangerousPatternVisitor(ast.NodeVisitor):
|
|
154
|
+
"""AST visitor that detects dangerous code patterns.
|
|
155
|
+
|
|
156
|
+
Patterns are configurable via onetool.yaml security section.
|
|
157
|
+
Supports fnmatch wildcards (*, ?, [seq]).
|
|
158
|
+
|
|
159
|
+
Pattern matching is automatic based on structure:
|
|
160
|
+
- Patterns without dots match builtins and imports
|
|
161
|
+
- Patterns with dots match qualified function calls
|
|
162
|
+
|
|
163
|
+
Two-tier priority: allow > warned > blocked
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
def __init__(
|
|
167
|
+
self,
|
|
168
|
+
blocked: frozenset[str] | None = None,
|
|
169
|
+
warned: frozenset[str] | None = None,
|
|
170
|
+
) -> None:
|
|
171
|
+
"""Initialize visitor with security patterns.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
blocked: Patterns that block execution (cause errors)
|
|
175
|
+
warned: Patterns that generate warnings (allow execution)
|
|
176
|
+
"""
|
|
177
|
+
self.errors: list[str] = []
|
|
178
|
+
self.warnings: list[str] = []
|
|
179
|
+
|
|
180
|
+
# Use provided patterns or defaults
|
|
181
|
+
blocked_all = blocked or DEFAULT_BLOCKED
|
|
182
|
+
warned_all = warned or DEFAULT_WARNED
|
|
183
|
+
|
|
184
|
+
# Split patterns by type for efficient matching
|
|
185
|
+
# Patterns without dots → match builtins and imports
|
|
186
|
+
# Patterns with dots → match qualified calls
|
|
187
|
+
self.blocked_simple = frozenset(p for p in blocked_all if not _has_dot(p))
|
|
188
|
+
self.blocked_qualified = frozenset(p for p in blocked_all if _has_dot(p))
|
|
189
|
+
self.warned_simple = frozenset(p for p in warned_all if not _has_dot(p))
|
|
190
|
+
self.warned_qualified = frozenset(p for p in warned_all if _has_dot(p))
|
|
191
|
+
|
|
192
|
+
def visit_Call(self, node: ast.Call) -> None:
|
|
193
|
+
"""Check function calls for dangerous patterns.
|
|
194
|
+
|
|
195
|
+
Priority order: blocked > warned (allow handled at setup)
|
|
196
|
+
"""
|
|
197
|
+
func_name = self._get_call_name(node)
|
|
198
|
+
|
|
199
|
+
if not func_name:
|
|
200
|
+
self.generic_visit(node)
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
# Determine which pattern sets to check based on call type
|
|
204
|
+
is_qualified = "." in func_name
|
|
205
|
+
|
|
206
|
+
if is_qualified:
|
|
207
|
+
# Qualified call (e.g., subprocess.run) - check qualified patterns
|
|
208
|
+
# Priority: blocked > warned
|
|
209
|
+
if pattern := _matches_pattern(func_name, self.blocked_qualified):
|
|
210
|
+
self.errors.append(
|
|
211
|
+
f"Line {node.lineno}: Dangerous function '{func_name}' is not "
|
|
212
|
+
f"allowed (matches '{pattern}')"
|
|
213
|
+
)
|
|
214
|
+
elif pattern := _matches_pattern(func_name, self.warned_qualified):
|
|
215
|
+
self.warnings.append(
|
|
216
|
+
f"Line {node.lineno}: Potentially unsafe function '{func_name}' "
|
|
217
|
+
f"(matches '{pattern}')"
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
# Simple call (e.g., exec) - check simple patterns (builtins)
|
|
221
|
+
# Priority: blocked > warned
|
|
222
|
+
if pattern := _matches_pattern(func_name, self.blocked_simple):
|
|
223
|
+
self.errors.append(
|
|
224
|
+
f"Line {node.lineno}: Dangerous builtin '{func_name}' is not "
|
|
225
|
+
f"allowed (matches '{pattern}')"
|
|
226
|
+
)
|
|
227
|
+
elif pattern := _matches_pattern(func_name, self.warned_simple):
|
|
228
|
+
self.warnings.append(
|
|
229
|
+
f"Line {node.lineno}: Potentially unsafe function '{func_name}' "
|
|
230
|
+
f"(matches '{pattern}')"
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Continue visiting child nodes
|
|
234
|
+
self.generic_visit(node)
|
|
235
|
+
|
|
236
|
+
def visit_Import(self, node: ast.Import) -> None:
|
|
237
|
+
"""Check imports for dangerous modules."""
|
|
238
|
+
for alias in node.names:
|
|
239
|
+
# Check simple patterns (no dots) against import names
|
|
240
|
+
if pattern := _matches_pattern(alias.name, self.blocked_simple):
|
|
241
|
+
self.errors.append(
|
|
242
|
+
f"Line {node.lineno}: Import of '{alias.name}' is not allowed "
|
|
243
|
+
f"(matches '{pattern}')"
|
|
244
|
+
)
|
|
245
|
+
elif pattern := _matches_pattern(alias.name, self.warned_simple):
|
|
246
|
+
self.warnings.append(
|
|
247
|
+
f"Line {node.lineno}: Import of '{alias.name}' may enable "
|
|
248
|
+
f"dangerous operations (matches '{pattern}')"
|
|
249
|
+
)
|
|
250
|
+
self.generic_visit(node)
|
|
251
|
+
|
|
252
|
+
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
|
253
|
+
"""Check from imports for dangerous modules."""
|
|
254
|
+
if node.module:
|
|
255
|
+
# Check simple patterns against the module name
|
|
256
|
+
if pattern := _matches_pattern(node.module, self.blocked_simple):
|
|
257
|
+
self.errors.append(
|
|
258
|
+
f"Line {node.lineno}: Import from '{node.module}' is not allowed "
|
|
259
|
+
f"(matches '{pattern}')"
|
|
260
|
+
)
|
|
261
|
+
elif pattern := _matches_pattern(node.module, self.warned_simple):
|
|
262
|
+
self.warnings.append(
|
|
263
|
+
f"Line {node.lineno}: Import from '{node.module}' may enable "
|
|
264
|
+
f"dangerous operations (matches '{pattern}')"
|
|
265
|
+
)
|
|
266
|
+
self.generic_visit(node)
|
|
267
|
+
|
|
268
|
+
def _get_call_name(self, node: ast.Call) -> str:
|
|
269
|
+
"""Extract the full name of a function call.
|
|
270
|
+
|
|
271
|
+
Handles:
|
|
272
|
+
- Simple calls: func()
|
|
273
|
+
- Attribute calls: module.func()
|
|
274
|
+
- Chained calls: module.submodule.func()
|
|
275
|
+
"""
|
|
276
|
+
if isinstance(node.func, ast.Name):
|
|
277
|
+
return node.func.id
|
|
278
|
+
elif isinstance(node.func, ast.Attribute):
|
|
279
|
+
parts: list[str] = [node.func.attr]
|
|
280
|
+
current = node.func.value
|
|
281
|
+
while isinstance(current, ast.Attribute):
|
|
282
|
+
parts.append(current.attr)
|
|
283
|
+
current = current.value
|
|
284
|
+
if isinstance(current, ast.Name):
|
|
285
|
+
parts.append(current.id)
|
|
286
|
+
return ".".join(reversed(parts))
|
|
287
|
+
return ""
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def validate_python_code(
|
|
291
|
+
code: str,
|
|
292
|
+
check_security: bool = True,
|
|
293
|
+
lint_warnings: bool = False,
|
|
294
|
+
filename: str = "<string>",
|
|
295
|
+
) -> ValidationResult:
|
|
296
|
+
"""Validate Python code for syntax and security issues.
|
|
297
|
+
|
|
298
|
+
Security patterns are loaded from onetool.yaml configuration.
|
|
299
|
+
If config is not available, built-in defaults are used.
|
|
300
|
+
Patterns support fnmatch wildcards (*, ?, [seq]).
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
code: Python code to validate
|
|
304
|
+
check_security: Whether to check for dangerous patterns (default True)
|
|
305
|
+
lint_warnings: Whether to include Ruff style warnings (default False)
|
|
306
|
+
filename: Filename for error messages
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
ValidationResult with valid flag, errors, and warnings
|
|
310
|
+
"""
|
|
311
|
+
result = ValidationResult()
|
|
312
|
+
|
|
313
|
+
# Step 1: Syntax validation
|
|
314
|
+
try:
|
|
315
|
+
tree = ast.parse(code, filename=filename)
|
|
316
|
+
result.ast_tree = tree
|
|
317
|
+
except SyntaxError as e:
|
|
318
|
+
result.valid = False
|
|
319
|
+
line_info = f" at line {e.lineno}" if e.lineno else ""
|
|
320
|
+
result.errors.append(f"Syntax error{line_info}: {e.msg}")
|
|
321
|
+
return result
|
|
322
|
+
|
|
323
|
+
# Step 2: Security pattern detection
|
|
324
|
+
if check_security:
|
|
325
|
+
# Load patterns from config or use defaults
|
|
326
|
+
security_config = _get_security_config()
|
|
327
|
+
|
|
328
|
+
if security_config is not None and security_config.enabled:
|
|
329
|
+
# Merge config patterns with defaults (additive behavior)
|
|
330
|
+
# This prevents accidental removal of critical security patterns
|
|
331
|
+
allow_set = frozenset(security_config.allow)
|
|
332
|
+
warned_set = frozenset(security_config.warned)
|
|
333
|
+
|
|
334
|
+
# Pattern priority (highest to lowest):
|
|
335
|
+
# 1. allow - completely exempt, no action
|
|
336
|
+
# 2. warned (user) - downgrades blocked defaults to warnings
|
|
337
|
+
# 3. blocked - prevents execution
|
|
338
|
+
# 4. warned (default) - generates warnings
|
|
339
|
+
#
|
|
340
|
+
# This lets users:
|
|
341
|
+
# - Add to blocked/warned (extends defaults)
|
|
342
|
+
# - Downgrade blocked→warned (e.g., warned: [os.popen])
|
|
343
|
+
# - Exempt entirely (e.g., allow: [open])
|
|
344
|
+
blocked = (DEFAULT_BLOCKED | set(security_config.blocked)) - allow_set - warned_set
|
|
345
|
+
warned = (DEFAULT_WARNED | set(security_config.warned)) - allow_set
|
|
346
|
+
|
|
347
|
+
visitor = DangerousPatternVisitor(
|
|
348
|
+
blocked=frozenset(blocked),
|
|
349
|
+
warned=frozenset(warned),
|
|
350
|
+
)
|
|
351
|
+
elif security_config is not None and not security_config.enabled:
|
|
352
|
+
# Security disabled in config - skip validation
|
|
353
|
+
visitor = None
|
|
354
|
+
else:
|
|
355
|
+
# No config - use defaults
|
|
356
|
+
visitor = DangerousPatternVisitor()
|
|
357
|
+
|
|
358
|
+
if visitor is not None:
|
|
359
|
+
visitor.visit(tree)
|
|
360
|
+
|
|
361
|
+
if visitor.errors:
|
|
362
|
+
result.valid = False
|
|
363
|
+
result.errors.extend(visitor.errors)
|
|
364
|
+
|
|
365
|
+
result.warnings.extend(visitor.warnings)
|
|
366
|
+
|
|
367
|
+
# Step 3: Optional Ruff linting (style warnings only)
|
|
368
|
+
if lint_warnings:
|
|
369
|
+
from ot.executor.linter import lint_code
|
|
370
|
+
|
|
371
|
+
lint_result = lint_code(code)
|
|
372
|
+
if lint_result.available:
|
|
373
|
+
result.warnings.extend(lint_result.warnings)
|
|
374
|
+
|
|
375
|
+
return result
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def validate_for_exec(code: str) -> ValidationResult:
|
|
379
|
+
"""Validate code specifically for exec() execution.
|
|
380
|
+
|
|
381
|
+
This is a stricter validation that also checks for patterns
|
|
382
|
+
that are problematic in exec() context.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
code: Python code to validate
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
ValidationResult with validation status
|
|
389
|
+
"""
|
|
390
|
+
result = validate_python_code(code, check_security=True)
|
|
391
|
+
|
|
392
|
+
if not result.valid:
|
|
393
|
+
return result
|
|
394
|
+
|
|
395
|
+
# Additional exec-specific checks could go here
|
|
396
|
+
# For example, checking for top-level returns outside functions
|
|
397
|
+
|
|
398
|
+
return result
|