# Parallel Workers Running in a Sandbox

# Introduction

This vignette shows how to set up “sandboxed” parallel workers with limited access to the host system.

# Examples

## Example: Bubblewrap on Linux

This example sets up two parallel workers on Linux sandboxed using Bubblewrap.

    library(parallelly)

    bwrap_sandbox <- function(rscript = "*") {
      ro_binds <- function(dirs) {
        dirs <- unique(dirs[file_test("-d", dirs)])
        opts <- rep(dirs, each = 3L)
        opts[seq(from = 1, to = length(opts), by = 3)] <- "--ro-bind"
        opts
      }

      ro_rlibs_remap <- function(dirs = rev(rev(.libPaths())[-1])) {
        dirs <- unique(dirs[file_test("-d", dirs)])
        dirs2 <- sub(sprintf("^%s", Sys.getenv("HOME")), "/home/sandbox-user", dirs)
        opts <- rep(dirs, each = 3L)
        opts[seq(from = 1, to = length(opts), by = 3)] <- "--ro-bind"
        opts[seq(from = 3, to = length(opts), by = 3)] <- dirs2
        opts
      }

      args <- c("bwrap")

      ## Unshares
      ## Note, we cannot sandbox the network (--unshare-net), because
      ## PSOCK clusters communicate over socket connections
      unshares <- c(
        "--unshare-user",  # isolate user and group ids
        "--unshare-pid",   # isolate processes
        "--proc", "/proc",
        "--unshare-ipc"    # isolate process communication, e.g. shared memory
      )
      args <- c(args, unshares)

      ## Misc options
      opts <- c(
        "--dev", "/dev",   # mount host's /dev
        "--tmpfs", "/tmp"  # mount fresh, private, empty temporary directory
      )
      args <- c(args, opts)

      ## Read-only Linux mounts
      dirs <- c("/usr", "/bin", "/usr/bin", "/lib", "/lib64", "/etc/alternatives")

      ## Use host's R and Rscript (by read-only mounting R home folders)
      components <- c("bin", "lib", "doc", "etc", "include", "modules", "share")
      r_dirs <- unname(vapply(components, FUN = R.home, FUN.VALUE = NA_character_))
      r_dirs <- c(r_dirs, dirname(Sys.which("R")), dirname(Sys.which("Rscript")))
      r_dirs <- c(r_dirs, rev(.libPaths())[1])
      dirs <- c(dirs, r_dirs)
      args <- c(args, ro_binds(dirs))

      ## Remap HOME to fresh, private sandboxed HOME
      tmp_home <- tempfile(pattern = "sandbox-home-")
      dir.create(tmp_home)
      opts <- c(
        "--bind", tmp_home, "/home/sandbox-user",
        "--setenv", "HOME", "/home/sandbox-user",
        "--chdir", "/home/sandbox-user"
      )
      args <- c(args, opts)

      ## Read-only remapped non-system R library paths
      args <- c(args, ro_rlibs_remap())

      c(args, rscript)
    } ## bwrap_sandbox()

    ## Launch two parallel workers inside a Bubblewrap sandbox
    cl <- makeClusterPSOCK(2L, rscript = bwrap_sandbox("*"))
    print(cl)
    #> Socket cluster with 2 nodes on host 'localhost' (R version 4.5.2
    #> (2025-10-31), platform x86_64-pc-linux-gnu)

    host_user <- Sys.info()[["user"]]
    host_user
    #> "alice"

    worker_user <- unlist(parallel::clusterEvalQ(cl, Sys.info()[["user"]]))
    worker_user
    #> [1] "unknown" "unknown"