Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 12 additions & 24 deletions sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
def _record_audit(event):
try:
sandbox_audit.record_execution(event)
except OSError:
except Exception:
pass


Expand Down Expand Up @@ -71,14 +71,17 @@ def _safe_target(workdir: str, filename: str) -> str:
raise ValueError("filename must stay inside the sandbox")
return str(target)

def run(self, files: Dict[str, str], script: str) -> Dict[str, Optional[object]]:
def run(self, files: Dict[str, str], script: str, puzzle_id: Optional[str] = None) -> Dict[str, Optional[object]]:
job_id = str(uuid.uuid4())
started = time.monotonic()
if not isinstance(files, dict) or len(files) > MAX_FILES:
_record_audit({"job_id": job_id, "puzzle_id": puzzle_id, "runtime": os.getenv("LUX_DOCKER_RUNTIME", "runc"), "duration_ms": 0, "exit_code": None, "timed_out": False, "docker_failed": False, "result": "input_validation_failure"})
raise ValueError(f"at most {MAX_FILES} files are allowed")
if not isinstance(script, str) or len(script.encode("utf-8")) > MAX_SCRIPT_BYTES:
_record_audit({"job_id": job_id, "puzzle_id": puzzle_id, "runtime": os.getenv("LUX_DOCKER_RUNTIME", "runc"), "duration_ms": 0, "exit_code": None, "timed_out": False, "docker_failed": False, "result": "input_validation_failure"})
raise ValueError("sandbox script is too large")
workdir = tempfile.mkdtemp(prefix="lux-sandbox-")
job_id = str(uuid.uuid4())
started = time.monotonic()
event = {"job_id": job_id, "puzzle_id": puzzle_id, "runtime": os.getenv("LUX_DOCKER_RUNTIME", "runc"), "exit_code": None, "timed_out": False, "docker_failed": False, "passed": False, "result": "exception"}
try:
for filename, content in files.items():
target = self._safe_target(workdir, filename)
Expand All @@ -102,16 +105,7 @@ def run(self, files: Dict[str, str], script: str) -> Dict[str, Optional[object]]
"command": cmd,
"timed_out": True,
}
_record_audit(
{
"job_id": job_id,
"runtime": os.getenv("LUX_DOCKER_RUNTIME", "runc"),
"duration_ms": round((time.monotonic() - started) * 1000),
"exit_code": None,
"timed_out": True,
"passed": False,
}
)
event.update({"timed_out": True, "result": "timeout", "passed": False})
return result
stdout = completed.stdout.decode("utf-8", errors="ignore")
stderr = completed.stderr.decode("utf-8", errors="ignore")
Expand All @@ -123,18 +117,12 @@ def run(self, files: Dict[str, str], script: str) -> Dict[str, Optional[object]]
"command": cmd,
"timed_out": timed_out,
}
_record_audit(
{
"job_id": job_id,
"runtime": os.getenv("LUX_DOCKER_RUNTIME", "runc"),
"duration_ms": round((time.monotonic() - started) * 1000),
"exit_code": completed.returncode,
"timed_out": timed_out,
"passed": result["passed"],
}
)
docker_failed = completed.returncode == 125 or b"docker: Error" in completed.stderr
event.update({"exit_code": completed.returncode, "docker_failed": docker_failed, "passed": result["passed"], "result": "docker_failure" if docker_failed else ("success" if result["passed"] else "nonzero_exit")})
return result
finally:
event["duration_ms"] = round((time.monotonic() - started) * 1000)
_record_audit(event)
shutil.rmtree(workdir, ignore_errors=True)


Expand Down
12 changes: 9 additions & 3 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -810,8 +810,8 @@ def build_docker_command(source_dir):
return DockerSandbox().build_command(source_dir)


def run_docker(files, script):
return get_runtime().run(files, script)
def run_docker(files, script, puzzle_id=None):
return get_runtime().run(files, script, puzzle_id=puzzle_id)


def validate(puzzle, attempt, files):
Expand All @@ -827,7 +827,13 @@ def validate(puzzle, attempt, files):

if v == "script":
try:
result = run_docker(files, puzzle["test_script"])
try:
result = run_docker(files, puzzle["test_script"], puzzle_id=puzzle["id"])
except TypeError as error:
# Keep compatibility with integrations that still provide the old two-argument hook.
if "puzzle_id" not in str(error):
raise
result = run_docker(files, puzzle["test_script"])
return response(True, correct=result["passed"], output=result)
except Exception as e:
return response(False, error=str(e)), 500
Expand Down