test(agents): capture inline sandbox __import__ escape regression (#107) #110
@@ -0,0 +1,63 @@
|
||||
Feature: Inline-code sandbox restricts imports to the json module only
|
||||
As a host operating CleverAgents in safe mode
|
||||
I want inline tool code to be unable to import any module other than json
|
||||
So that a "type: tool" agent's code body or the python_exec built-in tool
|
||||
cannot reach the filesystem, network, or process facilities that
|
||||
§13.2.1's Restricted Built-ins table and §13.2.3's prohibited-capabilities
|
||||
list were written to keep out
|
||||
|
||||
# Regression test for issue #107:
|
||||
# ToolAgent._execute_python_code (cleveractors.agents.tool) builds the
|
||||
# inline-code sandbox's __builtins__ dict per docs/index.md §13.2.1's
|
||||
# "Restricted Built-ins for Inline Code" table, which lists a single
|
||||
# module-shaped facility — json — and states that table is exhaustive
|
||||
# ("no other built-in facilities MAY be exposed"). §13.2.3 additionally
|
||||
# prohibits, without qualification, "dynamic import of modules other than
|
||||
# those explicitly listed".
|
||||
#
|
||||
# Despite this, safe_globals["__builtins__"]["__import__"] is bound
|
||||
# directly to the real, unrestricted __import__ builtin (with the
|
||||
# accompanying comment "Allow imports for json"). Because __import__ is
|
||||
# the actual mechanism the `import` statement invokes, inline code can
|
||||
# write `import os` (or any other module) and reach exactly the
|
||||
# facilities the standard was written to keep out. Both documented
|
||||
# entry points into _execute_python_code share this gap: a "type: tool"
|
||||
# agent's inline `code:` body, and the exec_python-gated `python_exec`
|
||||
# built-in tool.
|
||||
#
|
||||
# Will be fixed by replacing the raw __import__ reference with a
|
||||
# restricted shim on bugfix/m1-inline-sandbox-import-restriction per
|
||||
# issue #107.
|
||||
|
||||
@tdd_issue @tdd_issue_107 @tdd_expected_fail
|
||||
Scenario: python_exec tool rejects "import os" instead of executing it
|
||||
Given a ToolAgent is configured with name "python_exec_import_escape_agent" and config
|
||||
"""
|
||||
{
|
||||
"tools": ["python_exec"],
|
||||
"exec_python": true
|
||||
}
|
||||
"""
|
||||
When I create the ToolAgent
|
||||
And I process a JSON message with the ToolAgent:
|
||||
"""
|
||||
{"tool": "python_exec", "args": {"code": "import os\nresult = os.getcwd()"}}
|
||||
"""
|
||||
Then the inline code's import of "os" should have been rejected by the sandbox
|
||||
|
||||
@tdd_issue @tdd_issue_107 @tdd_expected_fail
|
||||
Scenario: A tool agent's inline code body rejects "import os" instead of executing it
|
||||
Given a ToolAgent is configured with name "inline_code_import_escape_agent" and config
|
||||
"""
|
||||
{
|
||||
"tools": [
|
||||
{
|
||||
"name": "escape_tool",
|
||||
"code": "import os\nresult = os.getcwd()"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
When I create the ToolAgent
|
||||
And I process a message with the ToolAgent: "irrelevant trigger message"
|
||||
Then the inline code's import of "os" should have been rejected by the sandbox
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Step definitions for the inline-code sandbox import-escape regression (issue #107).
|
||||
|
||||
Reuses the shared ToolAgent configuration/execution steps from
|
||||
``tool_agent_steps.py`` and adds the one assertion specific to this
|
||||
regression: that importing a non-``json`` module from inline code is
|
||||
rejected with an :class:`ExecutionError`, the same category
|
||||
``ToolAgent._execute_python_code`` already uses for other prohibited-name
|
||||
access, rather than succeeding outright. See
|
||||
``inline_sandbox_import_restriction.feature`` for the scenarios this backs.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from behave import then
|
||||
|
||||
from cleveractors.core.exceptions import ExecutionError
|
||||
|
||||
|
||||
@then(
|
||||
'the inline code\'s import of "{module_name}" should have been rejected by the sandbox'
|
||||
)
|
||||
def step_assert_import_rejected(context: Any, module_name: str) -> None:
|
||||
assert isinstance(context.error, ExecutionError), (
|
||||
f"Expected inline code executing 'import {module_name}' to be "
|
||||
"rejected with an ExecutionError — the category "
|
||||
"ToolAgent._execute_python_code already uses for other "
|
||||
"prohibited-name access (docs/index.md §13.2.1 lists no import "
|
||||
"facility beyond `json`; §13.2.3 prohibits dynamic import of any "
|
||||
f"other module) — but got {context.error!r} with result "
|
||||
f"{context.result!r} instead. The sandbox's __import__ builtin is "
|
||||
f"the real, unrestricted import machinery and let {module_name!r} "
|
||||
"through."
|
||||
)
|
||||
Reference in New Issue
Block a user