From d349c5126133e616ea993b43e565a1fa280d7efa Mon Sep 17 00:00:00 2001 From: Dmitry Rubtsov Date: Tue, 19 May 2026 22:35:55 +0600 Subject: [PATCH] security: strip OPEN_TERMINAL_* from subprocess env --- open_terminal/env.py | 18 ++++++++++++++++++ open_terminal/main.py | 8 ++++---- open_terminal/utils/runner.py | 6 +++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/open_terminal/env.py b/open_terminal/env.py index b7bd1d1..0c212ac 100644 --- a/open_terminal/env.py +++ b/open_terminal/env.py @@ -3,6 +3,24 @@ from open_terminal import config +def sanitized_environ(extra: dict | None = None) -> dict: + """Return ``os.environ`` with all ``OPEN_TERMINAL_*`` variables stripped. + + Used when spawning subprocesses (user shells, executed commands, notebook + kernels, etc.) so that child processes cannot observe internal + configuration — notably ``OPEN_TERMINAL_API_KEY`` and its + ``OPEN_TERMINAL_API_KEY_FILE`` Docker-secret variant. + + *extra* is merged on top of the sanitized base. Caller-supplied values + win, including any ``OPEN_TERMINAL_*`` keys the caller explicitly chooses + to re-introduce. + """ + env = {k: v for k, v in os.environ.items() if not k.startswith("OPEN_TERMINAL_")} + if extra: + env.update(extra) + return env + + def _resolve_file_env(var: str, default: str = "") -> str: """Resolve an environment variable with Docker-secrets ``_FILE`` support. diff --git a/open_terminal/main.py b/open_terminal/main.py index 01c0796..a8e99ce 100644 --- a/open_terminal/main.py +++ b/open_terminal/main.py @@ -24,7 +24,7 @@ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from pydantic import BaseModel, Field -from open_terminal.env import API_KEY, BINARY_FILE_MIME_PREFIXES, CORS_ALLOWED_ORIGINS, ENABLE_NOTEBOOKS, ENABLE_SYSTEM_PROMPT, ENABLE_TERMINAL, EXECUTE_DESCRIPTION, EXECUTE_TIMEOUT, FILE_BROWSER_ROOT, LOG_DIR, MAX_TERMINAL_SESSIONS, MULTI_USER, OPEN_TERMINAL_INFO, PROCESS_LOG_RETENTION, SESSION_CWD_TTL, SYSTEM_PROMPT, TERMINAL_TERM +from open_terminal.env import API_KEY, BINARY_FILE_MIME_PREFIXES, CORS_ALLOWED_ORIGINS, ENABLE_NOTEBOOKS, ENABLE_SYSTEM_PROMPT, ENABLE_TERMINAL, EXECUTE_DESCRIPTION, EXECUTE_TIMEOUT, FILE_BROWSER_ROOT, LOG_DIR, MAX_TERMINAL_SESSIONS, MULTI_USER, OPEN_TERMINAL_INFO, PROCESS_LOG_RETENTION, SESSION_CWD_TTL, SYSTEM_PROMPT, TERMINAL_TERM, sanitized_environ from open_terminal.utils.runner import PipeRunner, ProcessRunner, create_runner from open_terminal.utils.fs import UserFS @@ -1174,7 +1174,7 @@ async def execute( session_cwd = _get_session_cwd(session_id, fs) if session_id else None cwd = fs.resolve_path(request.cwd, cwd=session_cwd) if request.cwd else (session_cwd or fs.home) - subprocess_env = {**os.environ, **request.env} if request.env else None + subprocess_env = sanitized_environ(request.env) runner = await create_runner( request.command, cwd, subprocess_env, run_as_user=fs.username ) @@ -1570,7 +1570,7 @@ async def create_terminal(request: Request): shell_cmd = [os.environ.get("SHELL", "/bin/sh")] cwd = session_cwd or os.getcwd() - spawn_env = os.environ.copy() + spawn_env = sanitized_environ() spawn_env.setdefault("TERM", TERMINAL_TERM) process = subprocess.Popen( shell_cmd, @@ -1601,7 +1601,7 @@ async def create_terminal(request: Request): else: # winpty shell = os.environ.get("COMSPEC", "cmd.exe") - spawn_env = os.environ.copy() + spawn_env = sanitized_environ() spawn_env.setdefault("TERM", TERMINAL_TERM) pty_proc = _WinPtyProcess.spawn( [shell], diff --git a/open_terminal/utils/runner.py b/open_terminal/utils/runner.py index 2e63868..c12cabc 100644 --- a/open_terminal/utils/runner.py +++ b/open_terminal/utils/runner.py @@ -7,6 +7,8 @@ import time from abc import ABC, abstractmethod +from open_terminal.env import sanitized_environ + try: import fcntl import pty @@ -207,9 +209,7 @@ class WinPtyRunner(ProcessRunner): """Spawn a command under a Windows pseudo-terminal (ConPTY via pywinpty).""" def __init__(self, command: str, cwd: str | None, env: dict | None): - spawn_env = os.environ.copy() - if env: - spawn_env.update(env) + spawn_env = sanitized_environ(env) # Determine the executable and arguments. # PtyProcess.spawn expects a list: [executable, *args]