All files / server/services docker-command-builder.ts

100% Statements 8/8
100% Branches 6/6
100% Functions 3/3
100% Lines 7/7

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92          30x 30x                                                           9x 9x 9x       8x                                                                 10x                                
import { realpathSync } from "node:fs";
import { COMPILER_MAX_ERRORS_FLAG } from "./compiler-diagnostics";
 
// Match the existing 64 MiB transient /tmp ceiling while keeping runtime-relative
// writes and the compiled executable off the host-backed source bind.
const SANDBOX_WORK_SIZE_MB = 64;
const SANDBOX_WORK_MAX_INODES = 4096;
 
/**
 * Docker Command Builder
 * 
 * Handles the construction of secure Docker run commands with all necessary
 * security constraints and resource limits for Arduino sketch execution.
 */
 
interface DockerRunOptions {
  sketchDir: string;
  memoryMB: number;
  user?: string;
  cpuLimit: string;
  pidsLimit: number;
  imageName: string;
  command: string[];
  containerName?: string;
  labels?: string[];
}
 
export class DockerCommandBuilder {
  /**
   * Builds a secure Docker run command with all security constraints
   * 
   * @param options - Docker run configuration
   * @returns Array of command arguments for spawn
   */
  static buildSecureRunCommand(options: DockerRunOptions): string[] {
    // Resolve symlinks so Docker Desktop on macOS gets the real path (e.g. /private/tmp not /tmp)
    let realSketchDir = options.sketchDir;
    try { realSketchDir = realpathSync(options.sketchDir); } catch { /* keep original */ }
    return [
      "run",
      "--rm", // Remove container after exit
      ...(options.containerName ? ["--name", options.containerName] : []),
      ...(options.labels ?? []).flatMap((label) => ["--label", label]),
      "-i", // Interactive mode for stdin
      "--network",
      "none", // No network access
      "--memory",
      `${options.memoryMB}m`, // Memory limit
      "--memory-swap",
      `${options.memoryMB}m`, // Disable swap
      "--cpus",
      options.cpuLimit, // CPU limit (e.g., "0.5" for 50%)
      "--pids-limit",
      String(options.pidsLimit), // Limit number of processes
      ...(options.user ? ["--user", options.user] : []),
      "--security-opt",
      "no-new-privileges", // Prevent privilege escalation
      "--cap-drop",
      "ALL", // Drop all Linux capabilities
      "--read-only", // Keep the container root filesystem immutable
      "--tmpfs",
      "/tmp:rw,nosuid,nodev,noexec,mode=1777,size=64m", // Only bounded transient runtime storage
      "--tmpfs",
      `/sandbox-work:rw,nosuid,nodev,exec,mode=1777,size=${SANDBOX_WORK_SIZE_MB}m,nr_inodes=${SANDBOX_WORK_MAX_INODES}`,
      "-v",
      `${realSketchDir}:/sandbox:ro`, // Source and headers are visible to the compiler, never writable by sketch code
      options.imageName,
      ...options.command, // Execution command
    ];
  }
 
  /**
   * Builds the compile and run command for Docker
   */
  static buildCompileAndRunCommand(maxLifetimeSeconds: number): string[] {
    return [
      // Independent hard lifetime: the container ends after this wall-clock
      // time even if the backend that would stop it has died. As PID 1 the
      // timeout takes the whole container down with it.
      "timeout",
      "--signal=KILL",
      String(maxLifetimeSeconds),
      "sh",
      "-c",
      // The echo marker is the only signal that compilation succeeded. This
      // matters because g++ stderr is redirected to stdout above; compiler
      // diagnostics must not be mistaken for runtime output.
      `g++ -I/sandbox /sandbox/sketch.cpp -o /sandbox-work/sketch -pthread ${COMPILER_MAX_ERRORS_FLAG} 2>&1 && echo '[[RUNTIME_START]]' && cd /sandbox-work && ./sketch`,
    ];
  }
}