Skip to content

[WIP] feat: support bwrap/seatbelt-based local sandbox runtime for Computer Use#5487

Open
Soulter wants to merge 1 commit intomasterfrom
feat/local-sandbox
Open

[WIP] feat: support bwrap/seatbelt-based local sandbox runtime for Computer Use#5487
Soulter wants to merge 1 commit intomasterfrom
feat/local-sandbox

Conversation

@Soulter
Copy link
Member

@Soulter Soulter commented Feb 26, 2026

Modifications / 改动点

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果


Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Summary by Sourcery

添加一个沙盒化的本地 computer-use 运行时,用于隔离每个会话的工作空间,并与现有的 shell 和 Python 工具集成。

新功能:

  • 引入沙盒化的本地计算机运行模式(local_sandboxed),在 Linux 上使用 bwrap,在 macOS 上使用 sandbox-exec(seatbelt),提供按会话划分的工作空间以及受限的文件系统写入。
  • 允许本地 shell 工具调用指定可选的工作目录,并在新的沙盒化本地运行时下运行。
  • 在配置和仪表板元数据中暴露新的 computer_use_runtime 选项(nonelocallocal_sandboxedsandbox)。
  • 支持每个会话和沙盒模式使用独立的本地 booter 实例,为不同会话提供隔离环境。
  • 在平台管理器中添加对 email 平台适配器加载的支持。

增强:

  • 通过强制创建工作空间、规范化工作目录、禁用 stdin,以及显式处理超时,提高本地 shell 和 Python 执行的安全性和健壮性。
Original summary in English

Summary by Sourcery

Add a sandboxed local computer-use runtime that isolates per-session workspaces and integrates with existing shell and Python tools.

New Features:

  • Introduce a sandboxed local computer runtime mode (local_sandboxed) using bwrap on Linux and sandbox-exec (seatbelt) on macOS, with per-session workspaces and restricted filesystem writes.
  • Allow local shell tool invocations to specify an optional working directory and to run under the new sandboxed local runtime.
  • Expose the new computer_use_runtime option (none, local, local_sandboxed, sandbox) in configuration and dashboard metadata.
  • Support separate local booter instances per session and sandbox mode, enabling isolated environments for different sessions.
  • Add email platform adapter loading support in the platform manager.

Enhancements:

  • Improve safety and robustness of local shell and Python execution by enforcing workspace creation, normalized working directories, stdin disabling, and explicit timeout handling.

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Feb 26, 2026
@Soulter Soulter changed the title feat: support bwrap/seatbelt-based local sandbox runtime for Computer Use [WIP] feat: support bwrap/seatbelt-based local sandbox runtime for Computer Use Feb 26, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Soulter, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the security of the agent's local execution capabilities by introducing a local_sandboxed runtime option. This new mode leverages native OS sandboxing tools like bwrap and seatbelt to confine agent operations to a dedicated workspace, thereby minimizing potential risks associated with running code directly on the host machine. The changes involve core component refactoring, dynamic booter management, and updates to configuration and user interface elements to support this more secure execution environment.

Highlights

  • New Local Sandboxed Runtime: Introduced a new local_sandboxed runtime option for agent computer use, providing enhanced security for local execution.
  • OS-Level Sandboxing Integration: Implemented support for OS-level sandboxing using bwrap on Linux and seatbelt on macOS to isolate local shell and Python executions.
  • Refactored Local Components: Refactored local shell, Python, and filesystem components to utilize a new LocalSandboxPolicy for consistent path resolution, command wrapping, and workspace management.
  • Dynamic Booter Management: Updated the local booter management to support multiple sandboxed instances per session, allowing for flexible and isolated environments.
  • Configuration and UI Updates: Added the local_sandboxed option to the computer_use_runtime configuration and updated relevant UI texts in both English and Chinese to reflect this new capability.
Changelog
  • astrbot/core/astr_agent_tool_exec.py
    • Updated the runtime check to include local_sandboxed when determining available local computer tools.
  • astrbot/core/astr_main_agent.py
    • Extended the computer_use_runtime documentation to list local_sandboxed as an available option.
    • Modified the condition for applying local environment tools to also activate for the local_sandboxed runtime.
  • astrbot/core/computer/booters/local.py
    • Added new imports for re, shlex, uuid, pathlib.Path, and typing.Literal.
    • Introduced SandboxBackend Literal type for specifying sandbox mechanisms.
    • Implemented helper functions _escape_seatbelt_string, _session_workspace_name, and _detect_sandbox_backend for sandbox setup.
    • Removed the _ensure_safe_path function, replacing it with more robust path handling.
    • Defined a new LocalSandboxPolicy dataclass to encapsulate sandbox configuration, workspace management, path resolution, and command wrapping logic.
    • Modified LocalShellComponent to accept a LocalSandboxPolicy, use shlex.split for commands, wrap commands with the policy, and handle subprocess.TimeoutExpired.
    • Modified LocalPythonComponent to accept a LocalSandboxPolicy and wrap Python commands using the policy.
    • Modified LocalFileSystemComponent methods (create_file, read_file, write_file, delete_file, list_dir) to use the LocalSandboxPolicy for path validation and manipulation, and to leverage pathlib.Path methods.
    • Updated LocalBooter's constructor to accept session_id and sandboxed parameters, build a LocalSandboxPolicy, ensure workspace creation, and log sandbox backend status.
  • astrbot/core/computer/computer_client.py
    • Replaced the single local_booter instance with a dictionary local_booters to manage multiple booter instances based on session ID and sandboxing status.
    • Updated get_local_booter to retrieve or create LocalBooter instances using session_id and a new sandboxed parameter.
  • astrbot/core/computer/tools/python.py
    • Updated the LocalPythonTool description to inform users about write restrictions in the local_sandboxed runtime.
    • Modified the LocalPythonTool.call method to dynamically determine the sandboxed status from the configuration and pass it to get_local_booter.
  • astrbot/core/computer/tools/shell.py
    • Updated the ExecuteShellTool description to inform users about write restrictions in the local_sandboxed runtime.
    • Added a cwd parameter to the ExecuteShellTool's parameters schema and call method for specifying the working directory.
    • Modified the ExecuteShellTool.call method to dynamically determine the sandboxed status from the configuration, pass it to get_local_booter, and pass the cwd parameter to the shell execution.
  • astrbot/core/config/default.py
    • Added local_sandboxed as a new option for provider_settings.computer_use_runtime with its corresponding Chinese label.
  • astrbot/core/platform/manager.py
    • Added an import for EmailPlatformAdapter within the platform loading logic.
  • dashboard/src/i18n/locales/en-US/features/config-metadata.json
    • Updated the English hint for computer_use_runtime to include a description of the new local_sandboxed option.
  • dashboard/src/i18n/locales/en-US/features/extension.json
    • Updated the English runtimeHint to explicitly mention Local Sandboxed as a valid runtime option.
  • dashboard/src/i18n/locales/zh-CN/features/config-metadata.json
    • Updated the Chinese hint for computer_use_runtime to include a description of the new local_sandboxed option.
  • dashboard/src/i18n/locales/zh-CN/features/extension.json
    • Updated the Chinese runtimeHint to explicitly mention local_sandboxed as a valid runtime option.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot dosubot bot added the area:core The bug / feature is about astrbot's core, backend label Feb 26, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 4 个安全问题,并给出了一些总体反馈:

安全问题

  • 检测到子进程函数 Popen 使用了非静态字符串。如果这些数据可以被恶意行为者控制,可能会导致命令注入。请审计此调用的使用,以确保其不受外部资源控制。你可以考虑使用 shlex.escape()。(link)
  • 检测到子进程函数 run 使用了非静态字符串。如果这些数据可以被恶意行为者控制,可能会导致命令注入。请审计此调用的使用,以确保其不受外部资源控制。你可以考虑使用 shlex.escape()。(link)
  • 检测到子进程函数 run 使用了非静态字符串。如果这些数据可以被恶意行为者控制,可能会导致命令注入。请审计此调用的使用,以确保其不受外部资源控制。你可以考虑使用 shlex.escape()。(link)
  • 检测到子进程函数 run 使用了用户可控数据。恶意行为者可以利用这一点进行命令注入。你可以考虑使用 shlex.quote()。(link)

总体评论

  • LocalSandboxPolicy.ensure_writable_path() 依赖 Path.is_relative_to(),该方法只在 Python 3.9+ 中可用;如果这个项目仍然需要兼容 3.8 或更早版本,就需要为这个检查提供一个回退实现。
  • seatbelt 后端将 sandbox profile 作为单个字符串参数传递给 sandbox-exec -p;请再次确认这种调用形式在你的目标运行环境中是被支持的,如果 sandbox-exec 期望的是文件路径,考虑将 profile 写入临时文件后再传递。
  • local_booters 是一个以 (session_id, sandboxed) 为 key 的全局字典,而且从不清理;在存在大量会话的长生命周期进程中,这个结构可能会无限增长,因此建议增加清理策略,或者在合适的场景下在会话之间复用 booter。
面向 AI 智能体的提示
Please address the comments from this code review:

## Overall Comments
- LocalSandboxPolicy.ensure_writable_path() relies on Path.is_relative_to(), which is only available in Python 3.9+; if this project still targets 3.8 or earlier you’ll need a fallback implementation for that check.
- The seatbelt backend passes the sandbox profile as a single string argument to `sandbox-exec -p`; please double-check that this invocation form is supported in your target environments, and consider writing the profile to a temp file if `sandbox-exec` expects a file path instead.
- local_booters is a global dict keyed by (session_id, sandboxed) and never pruned; in long-lived processes with many sessions this can grow unbounded, so consider adding a cleanup strategy or reusing booters across sessions where appropriate.

## Individual Comments

### Comment 1
<location path="astrbot/core/computer/booters/local.py" line_range="194-203" />
<code_context>
                proc = subprocess.Popen(
                    wrapped_command,
                    shell=False,
                    cwd=working_dir,
                    env=run_env,
                    stdin=subprocess.DEVNULL,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True,
                )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'Popen' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 2
<location path="astrbot/core/computer/booters/local.py" line_range="206-215" />
<code_context>
                result = subprocess.run(
                    wrapped_command,
                    shell=False,
                    cwd=working_dir,
                    env=run_env,
                    timeout=timeout,
                    stdin=subprocess.DEVNULL,
                    capture_output=True,
                    text=True,
                )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 3
<location path="astrbot/core/computer/booters/local.py" line_range="248-253" />
<code_context>
                result = subprocess.run(
                    wrapped_command,
                    cwd=working_dir,
                    timeout=timeout,
                    capture_output=True,
                    text=True,
                )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 4
<location path="astrbot/core/computer/booters/local.py" line_range="249" />
<code_context>
                    wrapped_command,
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-tainted-env-args):** Detected subprocess function 'run' with user controlled data. A malicious actor could leverage this to perform command injection. You may consider using 'shlex.quote()'.

*Source: opengrep*
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的代码审查有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈持续改进代码审查质量。
Original comment in English

Hey - I've found 4 security issues, and left some high level feedback:

Security issues:

  • Detected subprocess function 'Popen' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'run' with user controlled data. A malicious actor could leverage this to perform command injection. You may consider using 'shlex.quote()'. (link)

General comments:

  • LocalSandboxPolicy.ensure_writable_path() relies on Path.is_relative_to(), which is only available in Python 3.9+; if this project still targets 3.8 or earlier you’ll need a fallback implementation for that check.
  • The seatbelt backend passes the sandbox profile as a single string argument to sandbox-exec -p; please double-check that this invocation form is supported in your target environments, and consider writing the profile to a temp file if sandbox-exec expects a file path instead.
  • local_booters is a global dict keyed by (session_id, sandboxed) and never pruned; in long-lived processes with many sessions this can grow unbounded, so consider adding a cleanup strategy or reusing booters across sessions where appropriate.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- LocalSandboxPolicy.ensure_writable_path() relies on Path.is_relative_to(), which is only available in Python 3.9+; if this project still targets 3.8 or earlier you’ll need a fallback implementation for that check.
- The seatbelt backend passes the sandbox profile as a single string argument to `sandbox-exec -p`; please double-check that this invocation form is supported in your target environments, and consider writing the profile to a temp file if `sandbox-exec` expects a file path instead.
- local_booters is a global dict keyed by (session_id, sandboxed) and never pruned; in long-lived processes with many sessions this can grow unbounded, so consider adding a cleanup strategy or reusing booters across sessions where appropriate.

## Individual Comments

### Comment 1
<location path="astrbot/core/computer/booters/local.py" line_range="194-203" />
<code_context>
                proc = subprocess.Popen(
                    wrapped_command,
                    shell=False,
                    cwd=working_dir,
                    env=run_env,
                    stdin=subprocess.DEVNULL,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True,
                )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'Popen' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 2
<location path="astrbot/core/computer/booters/local.py" line_range="206-215" />
<code_context>
                result = subprocess.run(
                    wrapped_command,
                    shell=False,
                    cwd=working_dir,
                    env=run_env,
                    timeout=timeout,
                    stdin=subprocess.DEVNULL,
                    capture_output=True,
                    text=True,
                )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 3
<location path="astrbot/core/computer/booters/local.py" line_range="248-253" />
<code_context>
                result = subprocess.run(
                    wrapped_command,
                    cwd=working_dir,
                    timeout=timeout,
                    capture_output=True,
                    text=True,
                )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 4
<location path="astrbot/core/computer/booters/local.py" line_range="249" />
<code_context>
                    wrapped_command,
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-tainted-env-args):** Detected subprocess function 'run' with user controlled data. A malicious actor could leverage this to perform command injection. You may consider using 'shlex.quote()'.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 194 to 203
proc = subprocess.Popen(
command,
shell=shell,
wrapped_command,
shell=False,
cwd=working_dir,
env=run_env,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): 检测到子进程函数 Popen 使用了非静态字符串。如果这些数据可以被恶意行为者控制,可能会导致命令注入。请审计此调用的使用,以确保其不受外部资源控制。你可以考虑使用 shlex.escape()

Source: opengrep

Original comment in English

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'Popen' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

Comment on lines +206 to +215
result = subprocess.run(
wrapped_command,
shell=False,
cwd=working_dir,
env=run_env,
timeout=timeout,
stdin=subprocess.DEVNULL,
capture_output=True,
text=True,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): 检测到子进程函数 run 使用了非静态字符串。如果这些数据可以被恶意行为者控制,可能会导致命令注入。请审计此调用的使用,以确保其不受外部资源控制。你可以考虑使用 shlex.escape()

Source: opengrep

Original comment in English

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

Comment on lines 248 to 253
result = subprocess.run(
[os.environ.get("PYTHON", sys.executable), "-c", code],
wrapped_command,
cwd=working_dir,
timeout=timeout,
capture_output=True,
text=True,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): 检测到子进程函数 run 使用了非静态字符串。如果这些数据可以被恶意行为者控制,可能会导致命令注入。请审计此调用的使用,以确保其不受外部资源控制。你可以考虑使用 shlex.escape()

Source: opengrep

Original comment in English

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

try:
result = subprocess.run(
[os.environ.get("PYTHON", sys.executable), "-c", code],
wrapped_command,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-tainted-env-args): 检测到子进程函数 run 使用了用户可控数据。恶意行为者可以利用这一点进行命令注入。你可以考虑使用 shlex.quote()

Source: opengrep

Original comment in English

security (python.lang.security.audit.dangerous-subprocess-use-tainted-env-args): Detected subprocess function 'run' with user controlled data. A malicious actor could leverage this to perform command injection. You may consider using 'shlex.quote()'.

Source: opengrep

@dosubot
Copy link

dosubot bot commented Feb 26, 2026

Related Documentation

1 document(s) may need updating based on files changed in this PR:

AstrBotTeam's Space

pr4697的改动
View Suggested Changes
@@ -68,11 +68,35 @@
   - `FILE_UPLOAD_TOOL`:上传文件到沙盒环境
   - `FILE_DOWNLOAD_TOOL`:从沙盒环境下载文件
 
-- **`runtime="local"`** 提供以下工具:
+- **`runtime="local"`** 或 **`runtime="local_sandboxed"`** 提供以下工具:
   - `LOCAL_EXECUTE_SHELL_TOOL`:在本地环境中执行 Shell 命令
+    - 在 `local_sandboxed` 模式下,文件写入受限于工作区目录(`~/.astrbot/workspace/<session>`)
+    - 支持可选的 `cwd`(工作目录)参数
   - `LOCAL_PYTHON_TOOL`:在本地环境中执行 Python 代码
+    - 在 `local_sandboxed` 模式下,文件写入受限于工作区目录(`~/.astrbot/workspace/<session>`)
 
 这些工具在 SubAgent handoff 场景下可正常使用,与主 Agent 运行时动态挂载的工具保持一致。
+
+**本地沙箱增强模式(local_sandboxed)**
+
+`local_sandboxed` 运行时选项使用操作系统级沙箱机制增强本地执行的安全性:
+
+- **Linux 系统**:使用 `bwrap`(Bubblewrap)提供命名空间隔离
+- **macOS 系统**:使用 `sandbox-exec` 和 seatbelt 配置文件提供沙箱隔离
+- **工作区隔离**:文件写入操作严格限制在会话专属工作区(`~/.astrbot/workspace/<session>`)
+- **降级处理**:如果 `bwrap` 或 `sandbox-exec` 不可用,系统会记录警告并降级为仅限制文件系统访问至工作区
+
+**工作区配置**
+
+- 工作区根目录可通过环境变量自定义:
+  - `ASTRBOT_LOCAL_WORKSPACE_ROOT`(优先)
+  - `ASTRBOT_LOCAL_WORKSPACE`(备选)
+  - 默认路径:`~/.astrbot/workspace`
+- 每个会话获得独立的工作区子目录,确保会话间隔离
+
+**安全性说明**
+
+相比普通的 `local` 模式,`local_sandboxed` 通过操作系统级沙箱机制防止未授权访问工作区外的文件系统,提供更好的隔离性。
 
 **工具集构建逻辑(_build_handoff_toolset)**
 

[Accept] [Decline]

Note: You must be authenticated to accept/decline updates.

How did I do? Any feedback?  Join Discord

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a 'local_sandboxed' runtime option for the agent's computer use, enhancing security by restricting file system access to a designated workspace. The changes modify astr_agent_tool_exec.py, astr_main_agent.py, local.py, computer_client.py, python.py, shell.py, default.py, manager.py, and several i18n files to accommodate this new runtime. The code reviewer raised concerns about the sandbox configurations allowing global read access, potential incompatibility with Python versions older than 3.9 due to the use of pathlib.Path.is_relative_to(), and the lack of an eviction policy for the local_booters cache, which could lead to memory leaks.

Comment on lines +128 to +162
if self.backend == "bwrap":
return [
"bwrap",
"--die-with-parent",
"--new-session",
"--ro-bind",
"/",
"/",
"--bind",
str(self.workspace),
str(self.workspace),
"--proc",
"/proc",
"--dev",
"/dev",
"--chdir",
str(working_dir),
"--",
*command,
]

if self.backend == "seatbelt":
workspace_escaped = _escape_seatbelt_string(str(self.workspace))
profile = "\n".join(
[
"(version 1)",
"(deny default)",
'(import "system.sb")',
"(allow process*)",
"(allow file-read*)",
f'(allow file-write* (subpath "{workspace_escaped}"))',
"(allow network*)",
]
)
return ["sandbox-exec", "-p", profile, *command]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The sandbox configurations for both Linux and macOS allow global read access to the host filesystem. On Linux, the entire root / is mounted as read-only (--ro-bind / /). On macOS, the profile explicitly allows all file reads ((allow file-read*)). This allows a potentially compromised LLM to exfiltrate sensitive information from the host system, such as SSH keys, configuration files, and user data, even when running in "sandboxed" mode. A sandbox should ideally restrict both read and write access to the designated workspace.

Comment on lines +110 to +113
if self.sandboxed and not abs_path.is_relative_to(self.workspace):
raise PermissionError(
f"Write path is outside workspace: {self.workspace.as_posix()}"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The method pathlib.Path.is_relative_to() was introduced in Python 3.9. If this project supports Python versions older than 3.9, this call will raise an AttributeError, breaking the sandboxing feature for users on those versions. To maintain compatibility, you can use pathlib.Path.relative_to() within a try...except ValueError block to achieve the same check.

Suggested change
if self.sandboxed and not abs_path.is_relative_to(self.workspace):
raise PermissionError(
f"Write path is outside workspace: {self.workspace.as_posix()}"
)
if self.sandboxed:
try:
abs_path.relative_to(self.workspace)
except ValueError:
raise PermissionError(
f"Write path is outside workspace: {self.workspace.as_posix()}"
)

Comment on lines +107 to +111
def get_local_booter(session_id: str, sandboxed: bool = False) -> ComputerBooter:
key = (session_id, sandboxed)
if key not in local_booters:
local_booters[key] = LocalBooter(session_id=session_id, sandboxed=sandboxed)
return local_booters[key]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The local_booters dictionary is used as a cache but has no eviction policy. Since session_id is part of the key, this dictionary can grow indefinitely as new sessions are created, leading to a memory leak. Over time, this can degrade application performance and potentially cause it to run out of memory. Consider using a cache with an eviction policy, like an LRU (Least Recently Used) cache, to limit the number of LocalBooter instances stored in memory. Alternatively, if there is a session lifecycle management, LocalBooter instances should be removed when their corresponding session ends.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant