diff --git a/.jules/sentinel.md b/.jules/sentinel.md index c46ef11..6f749bf 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -4,3 +4,8 @@ **Vulnerability:** Telemetry HTTP endpoints (`/status`, `/`) were completely unprotected, allowing any local user to view training state, usage, and costs. **Learning:** Initial implementation prioritized ease of use and local-only binding (`127.0.0.1`) but neglected defense-in-depth requirements for multi-user or shared environments. **Prevention:** Always implement at least Basic Authentication for any endpoint exposing state or metadata, even if restricted to loopback. Use random session-specific credentials if no configuration is provided. + +## 2025-01-24 - Environment Leakage and Sandbox Escape in Unit Test Gate +**Vulnerability:** The unit test gatekeeper was passing the entire host environment to the subprocess executing generated code, and lacked checks for Python internal attributes like `__subclasses__`. +**Learning:** Even "optional" or "local" test scripts can become vectors for secret leakage (e.g., OPENAI_API_KEY) if they don't explicitly isolate the execution environment. Regex-based filtering is a baseline, but environment isolation is more robust. +**Prevention:** Always use a strict allowlist for environment variables when spawning subprocesses to execute untrusted or generated code. Include Python introspection attributes in blocklists for a basic defense-in-depth against sandbox escapes. diff --git a/heidi_engine/telemetry.py b/heidi_engine/telemetry.py index bb89122..d14e60c 100644 --- a/heidi_engine/telemetry.py +++ b/heidi_engine/telemetry.py @@ -732,10 +732,6 @@ def get_state(run_id: Optional[str] = None) -> Dict[str, Any]: "usage": get_default_usage(), } - # BOLT OPTIMIZATION: Check thread-safe state cache - cached = _state_cache.get(target_run_id, state_file) - if cached: - return cached try: with open(state_file) as f: diff --git a/scripts/03_unit_test_gate.py b/scripts/03_unit_test_gate.py index 7507b7e..aa6bf47 100755 --- a/scripts/03_unit_test_gate.py +++ b/scripts/03_unit_test_gate.py @@ -40,6 +40,7 @@ import subprocess import sys import tempfile +import textwrap from typing import Any, Dict, List, Tuple # ============================================================================= @@ -82,6 +83,10 @@ r"\bshutil\.(rmtree|move|copy|copy2|copyfile|copymode|copystat|chown)\b", r"\bpickle\.(load|loads)\b", r"\bshelve\.open\b", + # Internal attributes for sandbox escape + r"__subclasses__", + r"__globals__", + r"__builtins__", # File operations (specifically writing/appending) r"\bopen\s*\([^)]*,\s*(mode\s*=\s*)?['\"][^'\"r]*[wa+x]", ] @@ -229,7 +234,7 @@ def test_python_code(code: str, temp_dir: str, execution_timeout: int = 5) -> Tu sys.stderr = stderr_capture # Execute the user's code -{code} +{textwrap.indent(code, ' ')} sys.stdout = original_stdout sys.stderr = original_stderr @@ -257,13 +262,19 @@ def test_python_code(code: str, temp_dir: str, execution_timeout: int = 5) -> Tu # Try to execute with timeout try: + # BOLT SECURITY: Filter environment to prevent leakage of sensitive keys (e.g. OPENAI_API_KEY) + # to the generated code being tested. + allowed_env_keys = {"PATH", "PYTHONPATH", "LANG", "PYTHONIOENCODING"} + safe_env = {k: v for k, v in os.environ.items() if k in allowed_env_keys} + safe_env["PYTHONPATH"] = temp_dir # Ensure isolation and local imports + result = subprocess.run( [sys.executable, test_file], capture_output=True, text=True, timeout=execution_timeout, cwd=temp_dir, - env={**os.environ, "PYTHONPATH": temp_dir}, + env=safe_env, ) stdout = result.stdout