fix(sandbox): reject non-finite OpenSandbox timeouts (#5944)

This commit is contained in:
Stellar鱼 2026-09-28 15:16:16 +08:00 committed by GitHub
parent 1129ee4e8a
commit c917c2cc8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 35 additions and 7 deletions

View File

@ -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,

View File

@ -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))

View File

@ -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