human-eval-rust 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
human_eval/sandbox.py ADDED
@@ -0,0 +1,586 @@
1
+ """
2
+ Sandbox wrapper for Rust code evaluation in HumanEval.
3
+
4
+ This module provides secure isolation for running rustc commands on LLM-generated code.
5
+ Uses Firejail for isolation with explicit user choice for installation or fallback modes.
6
+
7
+ Copyright (c) 2025 Dave Tofflemire, SigilDERG Project
8
+ Version: 2.1.0
9
+ """
10
+
11
+ import os
12
+ import shutil
13
+ import subprocess
14
+ import sys
15
+ from typing import NamedTuple
16
+
17
+
18
+ class SandboxError(Exception):
19
+ """Raised when sandbox operations fail."""
20
+
21
+ pass
22
+
23
+
24
+ class FirejailStatus(NamedTuple):
25
+ """Result of Firejail availability check."""
26
+
27
+ available: bool
28
+ version: str | None
29
+ error: str | None
30
+
31
+
32
+ class InstallResult(NamedTuple):
33
+ """Result of Firejail installation attempt."""
34
+
35
+ success: bool
36
+ stdout: str
37
+ stderr: str
38
+ exit_code: int
39
+
40
+
41
+ # Cache for rustc validation (avoid checking on every call)
42
+ _host_rustc_validated = False
43
+ _firejail_validated = False
44
+
45
+
46
+ FIREJAIL_SECURITY_OPTS = [
47
+ "--seccomp",
48
+ "--caps.drop=all",
49
+ "--noroot",
50
+ "--rlimit-fsize=104857600",
51
+ "--rlimit-nproc=50",
52
+ "--rlimit-cpu=120",
53
+ "--read-only=/",
54
+ "--private-tmp",
55
+ "--nogroups",
56
+ ]
57
+
58
+
59
+ def check_firejail_available() -> FirejailStatus:
60
+ """
61
+ Check if Firejail is available and get version info.
62
+
63
+ Returns:
64
+ FirejailStatus with availability, version, and any error message.
65
+ """
66
+ try:
67
+ result = subprocess.run(
68
+ ["firejail", "--version"],
69
+ capture_output=True,
70
+ text=True,
71
+ timeout=5,
72
+ )
73
+ if result.returncode == 0:
74
+ # Extract version from output (first line typically contains version)
75
+ version_line = (
76
+ result.stdout.strip().split("\n")[0] if result.stdout else None
77
+ )
78
+ return FirejailStatus(available=True, version=version_line, error=None)
79
+ return FirejailStatus(
80
+ available=False,
81
+ version=None,
82
+ error=f"firejail returned exit code {result.returncode}: {result.stderr}",
83
+ )
84
+ except subprocess.TimeoutExpired:
85
+ return FirejailStatus(
86
+ available=False, version=None, error="firejail --version timed out"
87
+ )
88
+ except FileNotFoundError:
89
+ return FirejailStatus(
90
+ available=False, version=None, error="firejail not found in PATH"
91
+ )
92
+ except Exception as e:
93
+ return FirejailStatus(available=False, version=None, error=str(e))
94
+
95
+
96
+ def detect_package_manager() -> str | None:
97
+ """
98
+ Detect the available package manager for Firejail installation.
99
+
100
+ Returns:
101
+ Package manager command prefix or None if not detected.
102
+ """
103
+ # Check for common package managers in order of preference
104
+ package_managers = [
105
+ ("apt-get", ["sudo", "apt-get", "install", "-y", "firejail"]),
106
+ ("dnf", ["sudo", "dnf", "install", "-y", "firejail"]),
107
+ ("yum", ["sudo", "yum", "install", "-y", "firejail"]),
108
+ ("pacman", ["sudo", "pacman", "-S", "--noconfirm", "firejail"]),
109
+ ("zypper", ["sudo", "zypper", "install", "-y", "firejail"]),
110
+ ("apk", ["sudo", "apk", "add", "firejail"]),
111
+ ]
112
+
113
+ for name, _ in package_managers:
114
+ if shutil.which(name):
115
+ return name
116
+ return None
117
+
118
+
119
+ def get_install_command() -> list[str] | None:
120
+ """
121
+ Get the appropriate Firejail installation command for this system.
122
+
123
+ Returns:
124
+ Installation command as list or None if no package manager found.
125
+ """
126
+ pm = detect_package_manager()
127
+ if pm == "apt-get":
128
+ return ["sudo", "apt-get", "install", "-y", "firejail"]
129
+ elif pm == "dnf":
130
+ return ["sudo", "dnf", "install", "-y", "firejail"]
131
+ elif pm == "yum":
132
+ return ["sudo", "yum", "install", "-y", "firejail"]
133
+ elif pm == "pacman":
134
+ return ["sudo", "pacman", "-S", "--noconfirm", "firejail"]
135
+ elif pm == "zypper":
136
+ return ["sudo", "zypper", "install", "-y", "firejail"]
137
+ elif pm == "apk":
138
+ return ["sudo", "apk", "add", "firejail"]
139
+ return None
140
+
141
+
142
+ def attempt_firejail_install() -> InstallResult:
143
+ """
144
+ Attempt to install Firejail using the system package manager.
145
+
146
+ Returns:
147
+ InstallResult with success status, stdout, stderr, and exit code.
148
+ """
149
+ install_cmd = get_install_command()
150
+ if install_cmd is None:
151
+ return InstallResult(
152
+ success=False,
153
+ stdout="",
154
+ stderr="No supported package manager found (apt-get, dnf, yum, pacman, zypper, apk)",
155
+ exit_code=-1,
156
+ )
157
+
158
+ print(f"Attempting to install Firejail: {' '.join(install_cmd)}", file=sys.stderr)
159
+ try:
160
+ result = subprocess.run(
161
+ install_cmd,
162
+ capture_output=True,
163
+ text=True,
164
+ timeout=300, # 5 minutes max for installation
165
+ )
166
+ return InstallResult(
167
+ success=result.returncode == 0,
168
+ stdout=result.stdout,
169
+ stderr=result.stderr,
170
+ exit_code=result.returncode,
171
+ )
172
+ except subprocess.TimeoutExpired:
173
+ return InstallResult(
174
+ success=False,
175
+ stdout="",
176
+ stderr="Installation timed out after 5 minutes",
177
+ exit_code=-1,
178
+ )
179
+ except Exception as e:
180
+ return InstallResult(
181
+ success=False,
182
+ stdout="",
183
+ stderr=str(e),
184
+ exit_code=-1,
185
+ )
186
+
187
+
188
+ def prompt_sandbox_choice(firejail_error: str | None = None) -> str:
189
+ """
190
+ Present interactive choice to user when Firejail is unavailable.
191
+
192
+ Args:
193
+ firejail_error: Error message from Firejail check, if any.
194
+
195
+ Returns:
196
+ Chosen mode: "firejail", "none", or raises SystemExit on cancel.
197
+ """
198
+ print("\n" + "=" * 70, file=sys.stderr)
199
+ print("SANDBOX CONFIGURATION REQUIRED", file=sys.stderr)
200
+ print("=" * 70, file=sys.stderr)
201
+
202
+ if firejail_error:
203
+ print(f"\nFirejail is not available: {firejail_error}", file=sys.stderr)
204
+ else:
205
+ print("\nFirejail sandbox is not available on this system.", file=sys.stderr)
206
+
207
+ print("\nOptions:", file=sys.stderr)
208
+ print(" [1] Install Firejail (requires sudo, Linux only)", file=sys.stderr)
209
+ print(" [2] Cancel and exit", file=sys.stderr)
210
+ print(
211
+ " [3] Proceed WITHOUT sandboxing (UNSAFE - for trusted code only)",
212
+ file=sys.stderr,
213
+ )
214
+ print("\n" + "-" * 70, file=sys.stderr)
215
+
216
+ while True:
217
+ try:
218
+ choice = input("Enter choice [1/2/3]: ").strip()
219
+ except (EOFError, KeyboardInterrupt):
220
+ print("\nOperation cancelled by user.", file=sys.stderr)
221
+ raise SystemExit(1)
222
+
223
+ if choice == "1":
224
+ # Attempt installation
225
+ install_result = attempt_firejail_install()
226
+ if install_result.success:
227
+ # Verify installation worked
228
+ status = check_firejail_available()
229
+ if status.available:
230
+ print(
231
+ f"\n✓ Firejail installed successfully: {status.version}",
232
+ file=sys.stderr,
233
+ )
234
+ return "firejail"
235
+ else:
236
+ print(
237
+ f"\n✗ Firejail installation appeared to succeed but verification failed: {status.error}",
238
+ file=sys.stderr,
239
+ )
240
+ else:
241
+ print("\n" + "-" * 70, file=sys.stderr)
242
+ print("FIREJAIL INSTALLATION FAILED", file=sys.stderr)
243
+ print("-" * 70, file=sys.stderr)
244
+ print(f"Exit code: {install_result.exit_code}", file=sys.stderr)
245
+ if install_result.stderr:
246
+ # Show first 500 chars of stderr
247
+ stderr_preview = install_result.stderr[:500]
248
+ print(f"Error output:\n{stderr_preview}", file=sys.stderr)
249
+ print("-" * 70, file=sys.stderr)
250
+ print("\nInstallation failed. You may need to:", file=sys.stderr)
251
+ print(" - Run with elevated privileges", file=sys.stderr)
252
+ print(
253
+ " - Install Firejail manually from your distribution's package manager",
254
+ file=sys.stderr,
255
+ )
256
+ print(" - Visit: https://firejail.wordpress.com/", file=sys.stderr)
257
+ print("\nChoose another option:", file=sys.stderr)
258
+ print(" [2] Cancel and exit", file=sys.stderr)
259
+ print(" [3] Proceed WITHOUT sandboxing (UNSAFE)", file=sys.stderr)
260
+
261
+ elif choice == "2":
262
+ print("\nOperation cancelled by user.", file=sys.stderr)
263
+ raise SystemExit(1)
264
+
265
+ elif choice == "3":
266
+ print("\n" + "!" * 70, file=sys.stderr)
267
+ print("WARNING: PROCEEDING WITHOUT SANDBOX", file=sys.stderr)
268
+ print("!" * 70, file=sys.stderr)
269
+ print(
270
+ "\nYou are about to run untrusted LLM-generated code WITHOUT sandboxing.",
271
+ file=sys.stderr,
272
+ )
273
+ print(
274
+ "This is DANGEROUS and should only be done with trusted code.",
275
+ file=sys.stderr,
276
+ )
277
+ print("\nAre you sure you want to continue?", file=sys.stderr)
278
+
279
+ try:
280
+ confirm = input("Type 'yes' to confirm: ").strip().lower()
281
+ except (EOFError, KeyboardInterrupt):
282
+ print("\nOperation cancelled by user.", file=sys.stderr)
283
+ raise SystemExit(1)
284
+
285
+ if confirm == "yes":
286
+ print("\n⚠ Proceeding without sandboxing.", file=sys.stderr)
287
+ return "none"
288
+ else:
289
+ print(
290
+ "\nConfirmation not received. Please choose again:", file=sys.stderr
291
+ )
292
+ print(" [1] Install Firejail", file=sys.stderr)
293
+ print(" [2] Cancel and exit", file=sys.stderr)
294
+ print(" [3] Proceed WITHOUT sandboxing (UNSAFE)", file=sys.stderr)
295
+
296
+ else:
297
+ print("Invalid choice. Please enter 1, 2, or 3.", file=sys.stderr)
298
+
299
+
300
+ def resolve_sandbox_mode(
301
+ sandbox_mode: str | None,
302
+ allow_no_sandbox: bool = False,
303
+ non_interactive: bool = False,
304
+ ) -> str:
305
+ """
306
+ Resolve the sandbox mode based on availability and user preferences.
307
+
308
+ Args:
309
+ sandbox_mode: Requested mode ("firejail", "none", or None for auto-detect).
310
+ allow_no_sandbox: If True and non_interactive, allows unsandboxed mode without prompt.
311
+ non_interactive: If True, fails fast instead of prompting.
312
+
313
+ Returns:
314
+ Resolved sandbox mode ("firejail" or "none").
315
+
316
+ Raises:
317
+ SandboxError: If Firejail required but unavailable in non-interactive mode.
318
+ SystemExit: If user cancels interactive prompt.
319
+ """
320
+ # Explicit firejail mode
321
+ if sandbox_mode == "firejail":
322
+ status = check_firejail_available()
323
+ if not status.available:
324
+ raise SandboxError(
325
+ f"Firejail sandbox mode requested but not available: {status.error}\n"
326
+ "Install Firejail or use --sandbox-mode=none (UNSAFE)."
327
+ )
328
+ return "firejail"
329
+
330
+ # Explicit none mode
331
+ if sandbox_mode == "none":
332
+ if not allow_no_sandbox and not non_interactive:
333
+ print(
334
+ "\n⚠ WARNING: Sandboxing disabled via --sandbox-mode=none",
335
+ file=sys.stderr,
336
+ )
337
+ print("This is UNSAFE for untrusted LLM-generated code!", file=sys.stderr)
338
+ return "none"
339
+
340
+ # Auto-detect mode (sandbox_mode is None or "auto")
341
+ status = check_firejail_available()
342
+ if status.available:
343
+ print(f"Using Firejail sandboxing ({status.version})", file=sys.stderr)
344
+ return "firejail"
345
+
346
+ # Firejail not available - handle based on mode
347
+ if non_interactive:
348
+ if allow_no_sandbox:
349
+ print(
350
+ f"\n⚠ WARNING: Firejail unavailable ({status.error}), proceeding unsandboxed",
351
+ file=sys.stderr,
352
+ )
353
+ return "none"
354
+ else:
355
+ raise SandboxError(
356
+ f"Firejail not available: {status.error}\n"
357
+ "Install Firejail, or use --allow-no-sandbox to proceed unsafely."
358
+ )
359
+
360
+ # Interactive mode - prompt user
361
+ return prompt_sandbox_choice(status.error)
362
+
363
+
364
+ def run_rustc_with_firejail(
365
+ source_file: str,
366
+ output_binary: str,
367
+ command_args: list[str],
368
+ timeout: float = 30.0,
369
+ capture_output: bool = True,
370
+ ) -> subprocess.CompletedProcess:
371
+ """
372
+ Run rustc command using Firejail for sandboxing.
373
+
374
+ Args:
375
+ source_file: Path to the Rust source file
376
+ output_binary: Path to the output binary
377
+ command_args: Additional rustc arguments
378
+ timeout: Timeout in seconds
379
+ capture_output: Whether to capture stdout/stderr
380
+
381
+ Returns:
382
+ CompletedProcess with returncode, stdout, stderr
383
+ """
384
+ source_dir = os.path.dirname(os.path.abspath(source_file))
385
+
386
+ rustc_cmd = (
387
+ [
388
+ "rustc",
389
+ ]
390
+ + command_args
391
+ + [
392
+ os.path.basename(source_file),
393
+ "-o",
394
+ os.path.basename(output_binary),
395
+ ]
396
+ )
397
+
398
+ # Firejail command with restrictions
399
+ # Memory limit: 4GB (same as previous Docker config for H100 optimization)
400
+ firejail_cmd = (
401
+ [
402
+ "firejail",
403
+ "--quiet",
404
+ "--net=none", # No network
405
+ "--private", # Private filesystem
406
+ "--private-cwd", # Private working directory
407
+ "--rlimit-as=4294967296", # 4GB memory limit (matches Docker config)
408
+ f"--timeout={int(timeout)}", # Timeout
409
+ "--cwd",
410
+ source_dir,
411
+ ]
412
+ + FIREJAIL_SECURITY_OPTS
413
+ + rustc_cmd
414
+ )
415
+
416
+ try:
417
+ result = subprocess.run(
418
+ firejail_cmd,
419
+ cwd=source_dir,
420
+ capture_output=capture_output,
421
+ text=True,
422
+ timeout=timeout,
423
+ )
424
+ return result
425
+ except subprocess.TimeoutExpired:
426
+ return subprocess.CompletedProcess(
427
+ firejail_cmd, returncode=124, stdout="", stderr="Command timed out"
428
+ )
429
+
430
+
431
+ def run_binary_with_firejail(
432
+ binary_path: str,
433
+ timeout: float = 30.0,
434
+ capture_output: bool = True,
435
+ ) -> subprocess.CompletedProcess:
436
+ """
437
+ Run a compiled binary using Firejail for sandboxing.
438
+
439
+ Args:
440
+ binary_path: Path to the binary
441
+ timeout: Timeout in seconds
442
+ capture_output: Whether to capture stdout/stderr
443
+
444
+ Returns:
445
+ CompletedProcess with returncode, stdout, stderr
446
+ """
447
+ binary_dir = os.path.dirname(os.path.abspath(binary_path))
448
+ binary_name = os.path.basename(binary_path)
449
+
450
+ # Memory limit: 4GB (same as previous Docker config for H100 optimization)
451
+ firejail_cmd = (
452
+ [
453
+ "firejail",
454
+ "--quiet",
455
+ "--net=none",
456
+ "--private",
457
+ "--private-cwd",
458
+ "--rlimit-as=4294967296", # 4GB memory limit (matches Docker config)
459
+ f"--timeout={int(timeout)}",
460
+ "--cwd",
461
+ binary_dir,
462
+ ]
463
+ + FIREJAIL_SECURITY_OPTS
464
+ + [f"./{binary_name}"]
465
+ )
466
+
467
+ try:
468
+ result = subprocess.run(
469
+ firejail_cmd,
470
+ cwd=binary_dir,
471
+ capture_output=capture_output,
472
+ text=True,
473
+ timeout=timeout,
474
+ )
475
+ return result
476
+ except subprocess.TimeoutExpired:
477
+ return subprocess.CompletedProcess(
478
+ firejail_cmd, returncode=124, stdout="", stderr="Command timed out"
479
+ )
480
+
481
+
482
+ def run_rustc_sandboxed(
483
+ source_file: str,
484
+ output_binary: str,
485
+ command_args: list[str],
486
+ timeout: float = 30.0,
487
+ capture_output: bool = True,
488
+ sandbox_mode: str | None = None,
489
+ ) -> subprocess.CompletedProcess:
490
+ """
491
+ Run rustc command with sandboxing (Firejail or none).
492
+
493
+ Args:
494
+ source_file: Path to the Rust source file
495
+ output_binary: Path to the output binary
496
+ command_args: Additional rustc arguments
497
+ timeout: Timeout in seconds
498
+ capture_output: Whether to capture stdout/stderr
499
+ sandbox_mode: "firejail", "none", or None (auto-detect)
500
+
501
+ Returns:
502
+ CompletedProcess with returncode, stdout, stderr
503
+
504
+ Raises:
505
+ SandboxError: If sandboxing is required but unavailable
506
+ """
507
+ # Resolve sandbox mode if not already resolved
508
+ if sandbox_mode is None:
509
+ status = check_firejail_available()
510
+ if status.available:
511
+ sandbox_mode = "firejail"
512
+ else:
513
+ sandbox_mode = "none"
514
+ print(
515
+ f"[WARNING] Firejail not available ({status.error}). "
516
+ "Untrusted completions will run UNSANDBOXED.",
517
+ file=sys.stderr,
518
+ )
519
+
520
+ if sandbox_mode == "firejail":
521
+ return run_rustc_with_firejail(
522
+ source_file, output_binary, command_args, timeout, capture_output
523
+ )
524
+ elif sandbox_mode == "none":
525
+ # No sandboxing - only for local development with trusted code
526
+ # Validate rustc is available on host (fail fast if missing)
527
+ global _host_rustc_validated
528
+ if not _host_rustc_validated:
529
+ if shutil.which("rustc") is None:
530
+ raise SandboxError(
531
+ "rustc not found in PATH. "
532
+ "Install Rust toolchain or use sandbox_mode='firejail'."
533
+ )
534
+ _host_rustc_validated = True
535
+
536
+ rustc_cmd = ["rustc"] + command_args + [source_file, "-o", output_binary]
537
+ return subprocess.run(
538
+ rustc_cmd, capture_output=capture_output, text=True, timeout=timeout
539
+ )
540
+ else:
541
+ raise SandboxError(f"Unknown sandbox mode: {sandbox_mode}")
542
+
543
+
544
+ def run_binary_sandboxed(
545
+ binary_path: str,
546
+ timeout: float = 30.0,
547
+ capture_output: bool = True,
548
+ sandbox_mode: str | None = None,
549
+ ) -> subprocess.CompletedProcess:
550
+ """
551
+ Run a compiled binary with sandboxing (Firejail or none).
552
+
553
+ Args:
554
+ binary_path: Path to the binary
555
+ timeout: Timeout in seconds
556
+ capture_output: Whether to capture stdout/stderr
557
+ sandbox_mode: "firejail", "none", or None (auto-detect)
558
+
559
+ Returns:
560
+ CompletedProcess with returncode, stdout, stderr
561
+
562
+ Raises:
563
+ SandboxError: If sandboxing is required but unavailable
564
+ """
565
+ # Resolve sandbox mode if not already resolved
566
+ if sandbox_mode is None:
567
+ status = check_firejail_available()
568
+ if status.available:
569
+ sandbox_mode = "firejail"
570
+ else:
571
+ sandbox_mode = "none"
572
+ print(
573
+ f"[WARNING] Firejail not available ({status.error}). "
574
+ "Untrusted completions will run UNSANDBOXED.",
575
+ file=sys.stderr,
576
+ )
577
+
578
+ if sandbox_mode == "firejail":
579
+ return run_binary_with_firejail(binary_path, timeout, capture_output)
580
+ elif sandbox_mode == "none":
581
+ # No sandboxing - only for local development with trusted code
582
+ return subprocess.run(
583
+ [binary_path], capture_output=capture_output, text=True, timeout=timeout
584
+ )
585
+ else:
586
+ raise SandboxError(f"Unknown sandbox mode: {sandbox_mode}")