Spaces:
Sleeping
Sleeping
Download sandbox_runner.py from LostDecimal/alien-physics-openenv: direct link, hf CLI and curl.
- Browser
- Download file 9.75 kB
-
https://huggingface.co/spaces/LostDecimal/alien-physics-openenv/resolve/main/sandbox_runner.py
- Command line
-
hf download hf://spaces/LostDecimal/alien-physics-openenv/sandbox_runner.py
-
curl -L -o sandbox_runner.py https://huggingface.co/spaces/LostDecimal/alien-physics-openenv/resolve/main/sandbox_runner.py
9.75 kB
| """Child-process runner for submitted theory modules.""" | |
| from __future__ import annotations | |
| import ast | |
| import builtins | |
| import contextlib | |
| import importlib | |
| import importlib.util | |
| import io | |
| import json | |
| import math | |
| import statistics | |
| import sys | |
| import time | |
| import traceback | |
| from typing import Any | |
| BANNED_CALLS = {"breakpoint", "compile", "eval", "exec", "input", "open", "__import__"} | |
| def main() -> int: | |
| payload = json.loads(sys.stdin.read()) | |
| result = run_payload(payload) | |
| sys.stdout.write(json.dumps(result)) | |
| return 0 | |
| def run_payload(payload: dict[str, Any]) -> dict[str, Any]: | |
| started = time.perf_counter() | |
| stdout = io.StringIO() | |
| try: | |
| raw = payload["theory_module"] | |
| # Transparently render DSL JSON into a Theory class before execution | |
| try: | |
| from theory_dsl import render_theory_module | |
| code = render_theory_module(raw) | |
| except Exception: | |
| code = raw | |
| allowed_imports = tuple(payload.get("allowed_imports", ())) | |
| _preload_modules(allowed_imports) | |
| _validate_ast(code, allowed_imports) | |
| namespace = { | |
| "__builtins__": _safe_builtins(allowed_imports), | |
| "__name__": "submitted_theory", | |
| "__package__": None, | |
| "math": math, | |
| "statistics": statistics, | |
| } | |
| compiled = compile(code, "<submitted_theory>", "exec") | |
| with contextlib.redirect_stdout(stdout): | |
| exec(compiled, namespace, namespace) | |
| theory_cls = namespace.get("Theory") | |
| if theory_cls is None: | |
| raise ValueError("Submitted module must define class Theory") | |
| theory = theory_cls() | |
| history = payload.get("history", []) | |
| future = payload.get("future", []) | |
| window = { | |
| "history": history, | |
| "steps": len(future), | |
| "sensor_names": payload.get("sensor_names", []), | |
| } | |
| if not hasattr(theory, "fit"): | |
| raise ValueError("Theory must implement fit(history)") | |
| if not hasattr(theory, "predict"): | |
| raise ValueError("Theory must implement predict(window)") | |
| if not hasattr(theory, "log_prob"): | |
| raise ValueError("Theory must implement log_prob(observations)") | |
| fit_result = theory.fit(history) | |
| predictions = theory.predict(window) | |
| reported_log_prob = theory.log_prob(future) | |
| drift_detected = bool(theory.detect_drift(history)) if hasattr(theory, "detect_drift") else False | |
| description = str(theory.describe()) if hasattr(theory, "describe") else "" | |
| return { | |
| "ok": True, | |
| "error": "", | |
| "stdout": stdout.getvalue()[-4000:], | |
| "predictions": _jsonable(predictions), | |
| "reported_log_prob": float(reported_log_prob), | |
| "fit_result": _jsonable(fit_result), | |
| "description": description[:2000], | |
| "drift_detected": drift_detected, | |
| "runtime_ms": round((time.perf_counter() - started) * 1000, 3), | |
| } | |
| except Exception as exc: # pragma: no cover - details are asserted by parent tests. | |
| return { | |
| "ok": False, | |
| "error": f"{type(exc).__name__}: {exc}", | |
| "stdout": stdout.getvalue()[-4000:], | |
| "traceback": traceback.format_exc(limit=4)[-4000:], | |
| "predictions": [], | |
| "reported_log_prob": float("-inf"), | |
| "description": "", | |
| "drift_detected": False, | |
| "runtime_ms": round((time.perf_counter() - started) * 1000, 3), | |
| } | |
| def _preload_modules(allowed_imports: tuple[str, ...]) -> None: | |
| for name in ("numpy", "scipy"): | |
| if name in allowed_imports: | |
| try: | |
| importlib.import_module(name) | |
| except Exception: | |
| pass | |
| def _validate_ast(code: str, allowed_imports: tuple[str, ...]) -> None: | |
| tree = ast.parse(code) | |
| module_aliases: dict[str, str] = {} | |
| for node in ast.walk(tree): | |
| if isinstance(node, ast.Import): | |
| for alias in node.names: | |
| _require_allowed_import(alias.name, allowed_imports) | |
| module_aliases[alias.asname or alias.name.split(".", 1)[0]] = alias.name | |
| elif isinstance(node, ast.ImportFrom): | |
| if node.module is None: | |
| raise ImportError("Relative imports are not allowed") | |
| _require_allowed_import(node.module, allowed_imports) | |
| _require_allowed_fromlist(node.module, [alias.name for alias in node.names], allowed_imports) | |
| for alias in node.names: | |
| imported_name = f"{node.module}.{alias.name}" | |
| if imported_name in allowed_imports: | |
| module_aliases[alias.asname or alias.name] = imported_name | |
| elif isinstance(node, ast.Call) and isinstance(node.func, ast.Name): | |
| if node.func.id in BANNED_CALLS: | |
| raise PermissionError(f"Call to {node.func.id} is not allowed") | |
| if node.func.id == "getattr": | |
| _require_allowed_getattr(node, module_aliases, allowed_imports) | |
| elif isinstance(node, ast.Attribute) and node.attr.startswith("__"): | |
| raise PermissionError("Dunder attribute access is not allowed") | |
| elif isinstance(node, ast.Attribute): | |
| _require_allowed_attribute(node, module_aliases, allowed_imports) | |
| def _require_allowed_import(module_name: str, allowed_imports: tuple[str, ...]) -> None: | |
| if module_name not in allowed_imports: | |
| raise ImportError(f"Import '{module_name}' is not allowed") | |
| def _require_allowed_fromlist(module_name: str, fromlist: list[str] | tuple[str, ...], allowed_imports: tuple[str, ...]) -> None: | |
| for item in fromlist: | |
| if item == "*": | |
| raise ImportError("Wildcard imports are not allowed") | |
| submodule_name = f"{module_name}.{item}" | |
| if _looks_like_submodule(submodule_name) and submodule_name not in allowed_imports: | |
| raise ImportError(f"Import '{submodule_name}' is not allowed") | |
| def _looks_like_submodule(module_name: str) -> bool: | |
| try: | |
| return importlib.util.find_spec(module_name) is not None | |
| except (ImportError, AttributeError, ModuleNotFoundError, ValueError): | |
| return False | |
| def _require_allowed_attribute( | |
| node: ast.Attribute, | |
| module_aliases: dict[str, str], | |
| allowed_imports: tuple[str, ...], | |
| ) -> None: | |
| if not isinstance(node.value, ast.Name): | |
| return | |
| module_name = module_aliases.get(node.value.id) | |
| if not module_name: | |
| return | |
| submodule_name = f"{module_name}.{node.attr}" | |
| if _looks_like_submodule(submodule_name) and submodule_name not in allowed_imports: | |
| raise ImportError(f"Import '{submodule_name}' is not allowed") | |
| def _require_allowed_getattr( | |
| node: ast.Call, | |
| module_aliases: dict[str, str], | |
| allowed_imports: tuple[str, ...], | |
| ) -> None: | |
| if len(node.args) < 2 or not isinstance(node.args[1], ast.Constant) or not isinstance(node.args[1].value, str): | |
| return | |
| attr = node.args[1].value | |
| if attr.startswith("__"): | |
| raise PermissionError("Dunder attribute access is not allowed") | |
| if not isinstance(node.args[0], ast.Name): | |
| return | |
| module_name = module_aliases.get(node.args[0].id) | |
| if not module_name: | |
| return | |
| submodule_name = f"{module_name}.{attr}" | |
| if _looks_like_submodule(submodule_name) and submodule_name not in allowed_imports: | |
| raise ImportError(f"Import '{submodule_name}' is not allowed") | |
| def _safe_builtins(allowed_imports: tuple[str, ...]) -> dict[str, Any]: | |
| safe_names = [ | |
| "abs", | |
| "all", | |
| "any", | |
| "ArithmeticError", | |
| "AssertionError", | |
| "BaseException", | |
| "bool", | |
| "dict", | |
| "enumerate", | |
| "Exception", | |
| "filter", | |
| "float", | |
| "getattr", | |
| "hasattr", | |
| "int", | |
| "isinstance", | |
| "issubclass", | |
| "len", | |
| "list", | |
| "map", | |
| "max", | |
| "min", | |
| "object", | |
| "pow", | |
| "print", | |
| "property", | |
| "range", | |
| "repr", | |
| "round", | |
| "set", | |
| "slice", | |
| "sorted", | |
| "staticmethod", | |
| "str", | |
| "sum", | |
| "super", | |
| "tuple", | |
| "type", | |
| "ValueError", | |
| "zip", | |
| ] | |
| safe = {name: getattr(builtins, name) for name in safe_names} | |
| safe["__build_class__"] = builtins.__build_class__ | |
| safe["__import__"] = _limited_import(allowed_imports) | |
| return safe | |
| def _limited_import(allowed_imports: tuple[str, ...]): | |
| def import_hook(name, globals=None, locals=None, fromlist=(), level=0): | |
| if level != 0: | |
| raise ImportError("Relative imports are not allowed") | |
| _require_allowed_import(name, allowed_imports) | |
| _require_allowed_fromlist(name, fromlist or (), allowed_imports) | |
| return builtins.__import__(name, globals, locals, fromlist, level) | |
| return import_hook | |
| def _jsonable(value: Any) -> Any: | |
| if isinstance(value, dict): | |
| return {str(key): _jsonable(item) for key, item in value.items()} | |
| if isinstance(value, (list, tuple)): | |
| return [_jsonable(item) for item in value] | |
| if hasattr(value, "item"): | |
| try: | |
| return value.item() | |
| except Exception: | |
| pass | |
| if hasattr(value, "tolist"): | |
| try: | |
| return _jsonable(value.tolist()) | |
| except Exception: | |
| pass | |
| try: | |
| json.dumps(value) | |
| return value | |
| except TypeError: | |
| return repr(value) | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |