From c917c2cc8f0a702895acec6159b9511e48abe932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stellar=E9=B1=BC?= <2182712990@qq.com> Date: Mon, 28 Sep 2026 15:16:16 +0800 Subject: [PATCH] fix(sandbox): reject non-finite OpenSandbox timeouts (#5944) --- .../community/opensandbox/provider.py | 15 +++++++++---- backend/tests/test_opensandbox_provider.py | 21 +++++++++++++++++++ config.example.yaml | 6 +++--- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/backend/packages/harness/deerflow/community/opensandbox/provider.py b/backend/packages/harness/deerflow/community/opensandbox/provider.py index 676f671e94..0dd9cb9747 100644 --- a/backend/packages/harness/deerflow/community/opensandbox/provider.py +++ b/backend/packages/harness/deerflow/community/opensandbox/provider.py @@ -5,6 +5,7 @@ from __future__ import annotations import atexit import ipaddress import logging +import math import os import threading import time @@ -91,8 +92,11 @@ class OpenSandboxProvider(WarmPoolLifecycleMixin[OpenSandboxSandbox], SandboxPro @staticmethod def _positive_float(name: str, value: Any, default: float) -> float: - resolved = float(default if value is None else value) - if resolved <= 0: + try: + resolved = float(default if value is None else value) + except (TypeError, ValueError) as exc: + raise ValueError(f"sandbox.{name} must be positive") from exc + if not math.isfinite(resolved) or resolved <= 0: raise ValueError(f"sandbox.{name} must be positive") return resolved @@ -115,8 +119,11 @@ class OpenSandboxProvider(WarmPoolLifecycleMixin[OpenSandboxSandbox], SandboxPro replicas = option("replicas") idle_timeout = option("idle_timeout") raw_sandbox_timeout = option("sandbox_timeout") - sandbox_timeout = float(DEFAULT_SANDBOX_TIMEOUT if raw_sandbox_timeout is None else raw_sandbox_timeout) - if sandbox_timeout < 0: + try: + sandbox_timeout = float(DEFAULT_SANDBOX_TIMEOUT if raw_sandbox_timeout is None else raw_sandbox_timeout) + except (TypeError, ValueError) as exc: + raise ValueError("sandbox.sandbox_timeout must be non-negative") from exc + if not math.isfinite(sandbox_timeout) or sandbox_timeout < 0: raise ValueError("sandbox.sandbox_timeout must be non-negative") return { "api_key": api_key, diff --git a/backend/tests/test_opensandbox_provider.py b/backend/tests/test_opensandbox_provider.py index 793004ec01..241843c27d 100644 --- a/backend/tests/test_opensandbox_provider.py +++ b/backend/tests/test_opensandbox_provider.py @@ -386,6 +386,27 @@ def test_null_sandbox_timeout_uses_default(monkeypatch: pytest.MonkeyPatch) -> N provider.shutdown() +@pytest.mark.parametrize( + ("config_key", "value", "message"), + [ + ("request_timeout", float("nan"), "sandbox.request_timeout must be positive"), + ("request_timeout", float("inf"), "sandbox.request_timeout must be positive"), + ("ready_timeout", float("nan"), "sandbox.ready_timeout must be positive"), + ("ready_timeout", float("inf"), "sandbox.ready_timeout must be positive"), + ("sandbox_timeout", float("nan"), "sandbox.sandbox_timeout must be non-negative"), + ("sandbox_timeout", float("inf"), "sandbox.sandbox_timeout must be non-negative"), + ], +) +def test_provider_rejects_non_finite_timeouts( + monkeypatch: pytest.MonkeyPatch, + config_key: str, + value: float, + message: str, +) -> None: + with pytest.raises(ValueError, match=message): + _install(monkeypatch, config={config_key: value}) + + @pytest.mark.parametrize("exit_code", [17, None]) def test_bootstrap_failure_destroys_created_remote(monkeypatch: pytest.MonkeyPatch, exit_code: int | None) -> None: sdk = _FakeSandboxClass(lambda index: _FakeRemote(f"remote-{index}", bootstrap_exit_code=exit_code)) diff --git a/config.example.yaml b/config.example.yaml index 7618feff17..7d79d1c9c4 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -1853,10 +1853,10 @@ sandbox: # # api_key: $OPEN_SANDBOX_API_KEY # optional when the SDK env var is set # # domain: localhost:8080 # optional; OPEN_SANDBOX_DOMAIN fallback # # protocol: http # localhost only; use https for remote domains -# # request_timeout: 30 # management API request timeout seconds -# # ready_timeout: 30 # create/readiness timeout seconds +# # request_timeout: 30 # finite positive management API request timeout seconds +# # ready_timeout: 30 # finite positive create/readiness timeout seconds # # use_server_proxy: false # proxy execd/file traffic through server -# # sandbox_timeout: 14400 # remote lifetime seconds; 0 = explicit cleanup +# # sandbox_timeout: 14400 # finite non-negative remote lifetime seconds; 0 = explicit cleanup # # bash_command_timeout: 600 # default remote command timeout seconds # # replicas: 3 # active + warm cap per gateway process # # idle_timeout: 600 # warm seconds before destroy; 0 disables