/ server / opensandbox_server / startup_guard.py
startup_guard.py
  1  # Copyright 2026 Alibaba Group Holding Ltd.
  2  #
  3  # Licensed under the Apache License, Version 2.0 (the "License");
  4  # you may not use this file except in compliance with the License.
  5  # You may obtain a copy of the License at
  6  #
  7  #     http://www.apache.org/licenses/LICENSE-2.0
  8  #
  9  # Unless required by applicable law or agreed to in writing, software
 10  # distributed under the License is distributed on an "AS IS" BASIS,
 11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  # See the License for the specific language governing permissions and
 13  # limitations under the License.
 14  
 15  import logging
 16  import os
 17  import sys
 18  import threading
 19  
 20  logger = logging.getLogger(__name__)
 21  
 22  ALLOW_NO_API_KEY_ENV = "OPENSANDBOX_INSECURE_SERVER"
 23  ALLOW_NO_API_KEY_CONFIRMATION = "YES"
 24  ANSI_RED = "\033[31m"
 25  ANSI_RESET = "\033[0m"
 26  API_KEY_CONFIRM_TIMEOUT_SECONDS = 30
 27  
 28  
 29  class _InputResult:
 30      def __init__(self) -> None:
 31          self.value: str | None = None
 32          self.error: BaseException | None = None
 33  
 34  
 35  def _read_with_timeout(prompt: str, input_func, timeout_seconds: int) -> str:
 36      result = _InputResult()
 37  
 38      def _worker() -> None:
 39          try:
 40              result.value = input_func(prompt)
 41          except BaseException as exc:  # pragma: no cover
 42              result.error = exc
 43  
 44      t = threading.Thread(target=_worker, daemon=True)
 45      t.start()
 46      t.join(timeout_seconds)
 47  
 48      if t.is_alive():
 49          raise TimeoutError(f"confirmation input timed out after {timeout_seconds} seconds")
 50      if result.error is not None:
 51          raise result.error
 52      return result.value or ""
 53  
 54  
 55  def api_key_confirm(
 56      *,
 57      configured_api_key: str | None,
 58      stdin=None,
 59      environ=None,
 60      input_func=input,
 61  ) -> None:
 62      """
 63      Enforce explicit confirmation before starting without server.api_key.
 64  
 65      Confirmation sources:
 66      1) OPENSANDBOX_INSECURE_SERVER=YES (non-interactive safe path)
 67      2) Interactive TTY prompt requiring exact input 'YES'
 68      """
 69      if configured_api_key and configured_api_key.strip():
 70          return
 71  
 72      env = environ if environ is not None else os.environ
 73  
 74      if env.get(ALLOW_NO_API_KEY_ENV) == ALLOW_NO_API_KEY_CONFIRMATION:
 75          logger.warning(
 76              "server.api_key is not configured. Proceeding only because %s=%s.",
 77              ALLOW_NO_API_KEY_ENV,
 78              ALLOW_NO_API_KEY_CONFIRMATION,
 79          )
 80          return
 81  
 82      stdin_stream = stdin if stdin is not None else sys.stdin
 83      if stdin_stream is not None and hasattr(stdin_stream, "isatty") and stdin_stream.isatty():
 84          try:
 85              confirmation = _read_with_timeout(
 86                  f"{ANSI_RED}"
 87                  "SECURITY WARNING: server.api_key is empty; API authentication is disabled. "
 88                  "Type 'YES' to continue startup without API key. "
 89                  "Strongly recommend setting server.api_key. "
 90                  "See: https://github.com/alibaba/OpenSandbox/issues/750 "
 91                  ": "
 92                  f"{ANSI_RESET}",
 93                  input_func,
 94                  API_KEY_CONFIRM_TIMEOUT_SECONDS,
 95              )
 96          except TimeoutError as exc:
 97              raise RuntimeError(
 98                  "Startup aborted: confirmation timed out waiting for YES. "
 99                  "Strongly recommend setting server.api_key. "
100                  "See: https://github.com/alibaba/OpenSandbox/issues/750"
101              ) from exc
102          if confirmation == ALLOW_NO_API_KEY_CONFIRMATION:
103              logger.warning(
104                  "server.api_key is not configured. Proceeding after interactive confirmation."
105              )
106              return
107          raise RuntimeError(
108              "Startup aborted: missing explicit confirmation for empty server.api_key. "
109              "Strongly recommend setting server.api_key. "
110              "See: https://github.com/alibaba/OpenSandbox/issues/750"
111          )
112  
113      raise RuntimeError(
114          "Startup blocked: server.api_key is empty in non-interactive mode. "
115          f"Set {ALLOW_NO_API_KEY_ENV}={ALLOW_NO_API_KEY_CONFIRMATION} to acknowledge the risk. "
116          "Strongly recommend setting server.api_key. "
117          "See: https://github.com/alibaba/OpenSandbox/issues/750"
118      )