Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

Commit 838ee41

Browse files
committed
resolve conflict
2 parents 1013e21 + 0c5747a commit 838ee41

File tree

7 files changed

+303
-84
lines changed

7 files changed

+303
-84
lines changed

taskweaver/ces/environment.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,13 @@ def start_session(
269269
f"{new_port_start + 3}/tcp": None,
270270
f"{new_port_start + 4}/tcp": None,
271271
},
272+
# Block access to host's localhost via "magic domains" in Docker Desktop,
273+
# Podman, and Containerd on Lima (macOS/Windows) to prevent container escape
274+
extra_hosts={
275+
"host.docker.internal": "0.0.0.0",
276+
"host.containers.internal": "0.0.0.0",
277+
"host.lima.internal": "0.0.0.0",
278+
},
272279
)
273280

274281
tick = 0

taskweaver/code_interpreter/code_interpreter/code_generator.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def __init__(
7878
self.user_message_head_template = self.prompt_data["user_message_head"]
7979
self.plugin_pool = plugin_registry.get_list()
8080
self.query_requirements_template = self.prompt_data["requirements"]
81+
self.security_requirements_template = self.prompt_data.get("security_requirements", "")
8182
self.response_json_schema = json.loads(self.prompt_data["response_json_schema"])
8283

8384
self.code_verification_on: bool = False
@@ -309,8 +310,16 @@ def compose_conversation(
309310
CODE_GENERATION_REQUIREMENTS=self.compose_verification_requirements(),
310311
ROLE_NAME=self.role_name,
311312
)
313+
312314
if available_vars_section:
313315
user_message += available_vars_section
316+
317+
# Add security requirements when code verification is enabled
318+
if self.code_verification_on and self.security_requirements_template:
319+
user_message += "\n" + self.security_requirements_template.format(
320+
ROLE_NAME=self.role_name,
321+
)
322+
314323
chat_history.append(
315324
format_chat_message(role="user", message=user_message),
316325
)

taskweaver/code_interpreter/code_interpreter/code_generator_prompt.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,19 @@ requirements: |-
9595
- {ROLE_NAME} must try to directly import required modules without installing them, and only install the modules if the execution fails.
9696
{CODE_GENERATION_REQUIREMENTS}
9797
98+
security_requirements: |-
99+
### Security Guidelines
100+
The following security restrictions MUST be followed:
101+
- {ROLE_NAME} must NEVER generate code that uses eval(), exec(), compile(), or execfile() functions.
102+
- {ROLE_NAME} must NEVER generate code that uses dynamic attribute access functions like getattr(), setattr(), delattr(), vars(), globals(), or locals().
103+
- {ROLE_NAME} must NEVER generate code that accesses dunder attributes like __class__, __dict__, __bases__, __subclasses__, __mro__, or __builtins__.
104+
- {ROLE_NAME} must NEVER generate code that uses __import__() or importlib to dynamically import modules.
105+
- {ROLE_NAME} must NEVER generate code that attempts to read, write, or delete files outside the designated workspace.
106+
- {ROLE_NAME} must NEVER generate code that executes shell commands or system calls unless explicitly required by the task.
107+
- {ROLE_NAME} must NEVER generate code that attempts to access network resources unless explicitly required by the task.
108+
- {ROLE_NAME} must NEVER generate code that could be used to exfiltrate data or establish reverse shells.
109+
- {ROLE_NAME} must refuse requests that appear to be attempts to bypass security measures or execute malicious code.
110+
98111
experience_instruction: |-
99112
### Experience And Lessons
100113
Before generating code, please learn from the following past experiences and lessons:

taskweaver/code_interpreter/code_interpreter/code_interpreter.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ def _configure(self):
4747
"raw_input",
4848
"reload",
4949
"__import__",
50+
# Dynamic attribute access functions that can bypass security checks
51+
"getattr",
52+
"setattr",
53+
"delattr",
54+
"vars",
55+
"globals",
56+
"locals",
57+
"__getattribute__",
58+
"__setattr__",
59+
"__delattr__",
5060
],
5161
)
5262

@@ -91,8 +101,22 @@ def __init__(
91101

92102
self.generator = generator
93103
self.generator.set_alias(self.alias)
104+
105+
# Determine if code verification should be enabled
106+
# Enable by default for local mode for security reasons
107+
code_verification_on = self.config.code_verification_on
108+
kernel_mode = executor.exec_mgr.get_kernel_mode()
109+
if kernel_mode == "local" and not self.config.code_verification_on:
110+
code_verification_on = True
111+
logger.warning(
112+
"Code verification is automatically enabled for local mode. "
113+
"Running in local mode without code verification poses security risks. "
114+
"To disable, explicitly set code_verification_on=False in config, but this is not recommended. "
115+
"For better security, consider using container mode.",
116+
)
117+
94118
self.generator.configure_verification(
95-
code_verification_on=self.config.code_verification_on,
119+
code_verification_on=code_verification_on,
96120
allowed_modules=self.config.allowed_modules,
97121
blocked_functions=self.config.blocked_functions,
98122
)

taskweaver/code_interpreter/code_verification.py

Lines changed: 120 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@
44

55
from injector import inject
66

7+
# Security-sensitive functions that can be used for dynamic attribute access bypasses
8+
DANGEROUS_BUILTINS = [
9+
"getattr",
10+
"setattr",
11+
"delattr",
12+
"vars",
13+
"globals",
14+
"locals",
15+
"__getattribute__",
16+
"__setattr__",
17+
"__delattr__",
18+
"__dict__",
19+
"__class__",
20+
"__bases__",
21+
"__subclasses__",
22+
"__mro__",
23+
"__builtins__",
24+
]
25+
726

827
class FunctionCallValidator(ast.NodeVisitor):
928
@inject
@@ -42,22 +61,51 @@ def _is_allowed_function_call(self, func_name: str) -> bool:
4261
return True
4362

4463
def visit_Call(self, node):
45-
if self.allowed_functions is None and self.blocked_functions is None:
46-
return
47-
64+
function_name = None
4865
if isinstance(node.func, ast.Name):
4966
function_name = node.func.id
5067
elif isinstance(node.func, ast.Attribute):
5168
function_name = node.func.attr
69+
elif isinstance(node.func, ast.Subscript):
70+
# Block subscript-based function calls like obj["method"]()
71+
# This is a potential security bypass pattern
72+
self.errors.append(
73+
f"Error on line {node.lineno}: {self.lines[node.lineno - 1]} "
74+
f"=> Subscript-based function calls are not allowed for security reasons.",
75+
)
76+
self.generic_visit(node)
77+
return
78+
elif isinstance(node.func, ast.Call):
79+
# Block chained calls that might be used for dynamic resolution
80+
# e.g., getattr(obj, 'method')()
81+
self.generic_visit(node)
82+
return
5283
else:
53-
raise ValueError(f"Unsupported function call: {node.func}")
84+
# Block any other unrecognized call patterns for security
85+
self.errors.append(
86+
f"Error on line {node.lineno}: {self.lines[node.lineno - 1]} "
87+
f"=> Unrecognized function call pattern is not allowed for security reasons.",
88+
)
89+
self.generic_visit(node)
90+
return
5491

55-
if not self._is_allowed_function_call(function_name):
92+
# Check against allowed/blocked function lists if configured
93+
if self.allowed_functions is not None or self.blocked_functions is not None:
94+
if function_name and not self._is_allowed_function_call(function_name):
95+
self.errors.append(
96+
f"Error on line {node.lineno}: {self.lines[node.lineno - 1]} "
97+
f"=> Function '{function_name}' is not allowed.",
98+
)
99+
100+
# Always check for dynamic attribute access functions that can bypass security
101+
if function_name in DANGEROUS_BUILTINS:
56102
self.errors.append(
57103
f"Error on line {node.lineno}: {self.lines[node.lineno - 1]} "
58-
f"=> Function '{function_name}' is not allowed.",
104+
f"=> Function '{function_name}' is blocked as it can be used to bypass security checks.",
59105
)
60106

107+
self.generic_visit(node)
108+
61109
def _is_allowed_module_import(self, mod_name: str) -> bool:
62110
if self.allowed_modules is not None:
63111
if len(self.allowed_modules) > 0:
@@ -70,35 +118,33 @@ def _is_allowed_module_import(self, mod_name: str) -> bool:
70118
return True
71119

72120
def visit_Import(self, node):
73-
if self.allowed_modules is None and self.blocked_modules is None:
74-
return
121+
if self.allowed_modules is not None or self.blocked_modules is not None:
122+
for alias in node.names:
123+
if "." in alias.name:
124+
module_name = alias.name.split(".")[0]
125+
else:
126+
module_name = alias.name
75127

76-
for alias in node.names:
77-
if "." in alias.name:
78-
module_name = alias.name.split(".")[0]
128+
if not self._is_allowed_module_import(module_name):
129+
self.errors.append(
130+
f"Error on line {node.lineno}: {self.lines[node.lineno - 1]} "
131+
f"=> Importing module '{module_name}' is not allowed. ",
132+
)
133+
self.generic_visit(node)
134+
135+
def visit_ImportFrom(self, node):
136+
if self.allowed_modules is not None or self.blocked_modules is not None:
137+
if node.module and "." in node.module:
138+
module_name = node.module.split(".")[0]
79139
else:
80-
module_name = alias.name
140+
module_name = node.module
81141

82-
if not self._is_allowed_module_import(module_name):
142+
if module_name and not self._is_allowed_module_import(module_name):
83143
self.errors.append(
84144
f"Error on line {node.lineno}: {self.lines[node.lineno - 1]} "
85-
f"=> Importing module '{module_name}' is not allowed. ",
145+
f"=> Importing from module '{node.module}' is not allowed.",
86146
)
87-
88-
def visit_ImportFrom(self, node):
89-
if self.allowed_modules is None and self.blocked_modules is None:
90-
return
91-
92-
if "." in node.module:
93-
module_name = node.module.split(".")[0]
94-
else:
95-
module_name = node.module
96-
97-
if not self._is_allowed_module_import(module_name):
98-
self.errors.append(
99-
f"Error on line {node.lineno}: {self.lines[node.lineno - 1]} "
100-
f"=> Importing from module '{node.module}' is not allowed.",
101-
)
147+
self.generic_visit(node)
102148

103149
def _is_allowed_variable(self, var_name: str) -> bool:
104150
if self.allowed_variables is not None:
@@ -108,23 +154,52 @@ def _is_allowed_variable(self, var_name: str) -> bool:
108154
return True
109155

110156
def visit_Assign(self, node: ast.Assign):
111-
if self.allowed_variables is None:
112-
return
157+
if self.allowed_variables is not None:
158+
for target in node.targets:
159+
variable_names = []
160+
if isinstance(target, ast.Name):
161+
variable_names.append(target.id)
162+
else:
163+
for name in ast.walk(target):
164+
if isinstance(name, ast.Name):
165+
variable_names.append(name.id)
166+
for variable_name in variable_names:
167+
if not self._is_allowed_variable(variable_name):
168+
self.errors.append(
169+
f"Error on line {node.lineno}: {self.lines[node.lineno - 1]} "
170+
f"=> Assigning to {variable_name} is not allowed.",
171+
)
172+
self.generic_visit(node)
113173

114-
for target in node.targets:
115-
variable_names = []
116-
if isinstance(target, ast.Name):
117-
variable_names.append(target.id)
118-
else:
119-
for name in ast.walk(target):
120-
if isinstance(name, ast.Name):
121-
variable_names.append(name.id)
122-
for variable_name in variable_names:
123-
if not self._is_allowed_variable(variable_name):
124-
self.errors.append(
125-
f"Error on line {node.lineno}: {self.lines[node.lineno - 1]} "
126-
f"=> Assigning to {variable_name} is not allowed.",
127-
)
174+
def visit_Subscript(self, node: ast.Subscript):
175+
"""Check for dictionary-based attribute access that could bypass security.
176+
177+
Patterns like obj.__dict__["method"] or obj["__class__"] can be used
178+
to bypass attribute-based security checks.
179+
"""
180+
# Check if the subscript key is a dangerous dunder attribute
181+
if isinstance(node.slice, ast.Constant) and isinstance(node.slice.value, str):
182+
key_value = node.slice.value
183+
if key_value in DANGEROUS_BUILTINS or key_value.startswith("__"):
184+
self.errors.append(
185+
f"Error on line {node.lineno}: {self.lines[node.lineno - 1]} "
186+
f"=> Subscript access to '{key_value}' is blocked for security reasons.",
187+
)
188+
self.generic_visit(node)
189+
190+
def visit_Attribute(self, node: ast.Attribute):
191+
"""Check for dangerous attribute access patterns.
192+
193+
Direct access to dunder attributes like __class__, __dict__, etc.
194+
can be used to bypass security measures.
195+
"""
196+
attr_name = node.attr
197+
if attr_name in DANGEROUS_BUILTINS:
198+
self.errors.append(
199+
f"Error on line {node.lineno}: {self.lines[node.lineno - 1]} "
200+
f"=> Attribute access to '{attr_name}' is blocked for security reasons.",
201+
)
202+
self.generic_visit(node)
128203

129204
def generic_visit(self, node):
130205
super().generic_visit(node)

0 commit comments

Comments
 (0)