Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
^\.devcontainer$
^\.github$
^\.claude$
^replr/tests/testthat/_snaps.*$
^tests/testthat/_snaps.*$
^doc$
^Meta$
7 changes: 4 additions & 3 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Use the official Rocker R image as base
FROM rocker/r-ver:4.4
# Image pinned by SHA256 digest for supply chain security (rocker/r-ver:4.4 as of 2025-11-25)
FROM rocker/r-ver@sha256:80bf33ee1b61f3252129912867abf23339ef1d9e96c837cbd86341a23c3fef07

# Install system dependencies required for R packages
RUN apt-get update -y && apt-get install -y \
Expand Down Expand Up @@ -27,7 +28,7 @@ RUN apt-get update -y && apt-get install -y \
# Set CRAN repository to improve package installation reliability
RUN echo 'options(repos = c(CRAN = "https://cran.rstudio.com/"))' >> /usr/local/lib/R/etc/Rprofile.site

# Install required R packages for replr in order of dependency complexity
# Install required R packages for repljail in order of dependency complexity
RUN Rscript -e "install.packages('R6', repos='https://cran.rstudio.com/', dependencies=TRUE)"
RUN Rscript -e "install.packages('uuid', repos='https://cran.rstudio.com/', dependencies=TRUE)"
RUN Rscript -e "install.packages('processx', repos='https://cran.rstudio.com/', dependencies=TRUE)"
Expand All @@ -41,7 +42,7 @@ RUN Rscript -e "install.packages(c('pryr', 'testthat'), repos='https://cran.rstu
RUN Rscript -e "install.packages(c('devtools', 'roxygen2', 'pkgdown', 'lintr', 'remotes', 'here', 'languageserver', 'httpgd', 'png', 'pak', 'ellmer', 'covr', 'knitr', 'rmarkdown', 'DT', 'htmltools'), repos='https://cran.rstudio.com/', dependencies=TRUE)"

# Set working directory
WORKDIR /workspaces/replr
WORKDIR /workspaces/repljail

# Add health check
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
Expand Down
90 changes: 0 additions & 90 deletions .devcontainer/README.md

This file was deleted.

2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "R Environment for replr",
"name": "R Environment for repljail",
"build": {
"dockerfile": "Dockerfile",
"context": ".."
Expand Down
8 changes: 4 additions & 4 deletions .devcontainer/test-environment.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Test script to validate the custom Copilot environment setup
# This script should be run inside the devcontainer to verify everything works

cat("=== Testing Custom GitHub Copilot Environment for replr ===\n\n")
cat("=== Testing Custom GitHub Copilot Environment for repljail ===\n\n")

# Test 1: Check R version
cat("1. R Version:\n")
Expand Down Expand Up @@ -43,19 +43,19 @@ for (pkg in dev_packages) {
}

# Test 5: Test package dependency check function
cat("\n5. Testing replr dependency check:\n")
cat("\n5. Testing repljail dependency check:\n")
tryCatch(
{
if (file.exists("R/utils.R")) {
source("R/utils.R")
result <- check_dependencies()
cat(" ✓ replr::check_dependencies() passed\n")
cat(" ✓ repljail::check_dependencies() passed\n")
} else {
cat(" ⚠ R/utils.R not found (run from package root)\n")
}
},
error = function(e) {
cat(sprintf(" ✗ replr::check_dependencies() failed: %s\n", e$message))
cat(sprintf(" ✗ repljail::check_dependencies() failed: %s\n", e$message))
}
)

Expand Down
56 changes: 28 additions & 28 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

# replr - Isolated R REPL
# repljail - Isolated R REPL

Process-based isolated R REPL implementation that provides secure, sandboxed R code execution using separate worker processes. Designed for running untrusted or potentially problematic R code without affecting the parent process.

Expand Down Expand Up @@ -42,10 +42,10 @@ cleanup_docker_containers()
cleanup_docker_networks()

# Enable Docker mode for workers
options(replr.worker.type = "docker")
options(repljail.worker.type = "docker")

# Enable network isolation (requires Docker mode)
options(replr.worker.docker.network.isolation = TRUE)
options(repljail.worker.docker.network.isolation = TRUE)
```

**Firejail Support (Linux)**
Expand All @@ -54,10 +54,10 @@ options(replr.worker.docker.network.isolation = TRUE)
is_firejail_available()

# Enable Firejail mode for workers
options(replr.worker.type = "firejail")
options(repljail.worker.type = "firejail")

# Use custom Firejail profile (optional)
options(replr.worker.firejail.profile = "/path/to/profile.profile")
options(repljail.worker.firejail.profile = "/path/to/profile.profile")
```

**macOS Sandbox Support (macOS only)**
Expand All @@ -66,13 +66,13 @@ options(replr.worker.firejail.profile = "/path/to/profile.profile")
is_macos_sandbox_available()

# Enable macOS sandbox mode for workers
options(replr.worker.type = "macos-sandbox")
options(repljail.worker.type = "macos-sandbox")

# Use custom sandbox profile (optional)
options(replr.worker.macos.sandbox.profile = "/path/to/profile.sb")
options(repljail.worker.macos.sandbox.profile = "/path/to/profile.sb")
```

**Worker Type Selection**: Set `replr.worker.type` to one of: `"native"`, `"docker"`, `"firejail"`, or `"macos-sandbox"` (default: `"native"`)
**Worker Type Selection**: Set `repljail.worker.type` to one of: `"native"`, `"docker"`, `"firejail"`, or `"macos-sandbox"` (default: `"native"`)

### Debug Logging
```r
Expand Down Expand Up @@ -123,43 +123,44 @@ Main R Process (Controller) ←──nanonext REQ/REP──→ Worker R Process
- **inst/worker.R**: Worker script that runs in isolated process, handles code evaluation

### Worker Process Details
- Located at `inst/worker.R` (or system.file("worker.R", package = "replr") when installed)
- Located at `inst/worker.R` (or system.file("worker.R", package = "repljail") when installed)
- Started with: `Rscript worker.R <port> [--debug] [--listen-all]`
- Uses `evaluate::evaluate()` with `stop_on_error = 2` (continues after errors)
- Plots captured via `recordedplot` objects, converted to base64 PNG data URLs
- Non-blocking message loop allows graceful shutdown via "__SHUTDOWN__" message

### Docker Integration
- Dockerfile in `inst/Dockerfile` based on `rocker/r-ver:4.4`
- **Supply chain security**: All base images pinned by SHA256 digest (rocker/r-ver, alpine/socat)
- Security features: non-root user, read-only filesystem, capability dropping, memory/CPU limits
- Container naming: `replr-worker-<port>-<timestamp>` for cleanup tracking
- Configurable via options: `replr.worker.docker.image`, `replr.worker.docker.memory`, `replr.worker.docker.cpus`
- **Network Isolation (Sidecar Pattern)**: Optional air-gapped execution with `replr.worker.docker.network.isolation`
- Container naming: `repljail-worker-<port>-<timestamp>` for cleanup tracking
- Configurable via options: `repljail.worker.docker.image`, `repljail.worker.docker.memory`, `repljail.worker.docker.cpus`
- **Network Isolation (Sidecar Pattern)**: Optional air-gapped execution with `repljail.worker.docker.network.isolation`
- Creates `--internal` bridge network (blocks all outbound traffic including internet)
- Worker container: Connected to isolated network only (zero internet access)
- Gateway sidecar: `alpine/socat` container that bridges host ↔ worker communication
- Gateway sidecar: `alpine/socat` container (pinned by SHA256) that bridges host ↔ worker communication
- Architecture:
```
Host (127.0.0.1:<port>) ← Docker publish → Gateway Container ← Internal Network → Worker Container (air-gapped)
```
- Network naming: `replr-network-<port>-<timestamp>`
- Gateway naming: `replr-gateway-<port>-<timestamp>`
- Network naming: `repljail-network-<port>-<timestamp>`
- Gateway naming: `repljail-gateway-<port>-<timestamp>`
- Automatic cleanup of worker, gateway, and network when session stops
- Security: Worker has ZERO external network access while host communication works via gateway proxy

### Firejail Integration (Linux)
- Lightweight sandboxing for Linux systems using firejail
- Implemented in `R/worker-wrappers.R` via `FirejailWorkerWrapper` R6 class
- Availability detection: `is_firejail_available()` checks for firejail command
- Configurable via options: `replr.worker.type = "firejail"`, `replr.worker.firejail.profile`
- Configurable via options: `repljail.worker.type = "firejail"`, `repljail.worker.firejail.profile`
- **Default Security Settings**:
- Network isolation: `--net=lo` (loopback only, blocks external access)
- Filesystem isolation: `--private-tmp` (isolated temp directory)
- Capability dropping: `--caps.drop=all` (drop all Linux capabilities)
- Seccomp filtering: `--seccomp` (restrict system calls)
- No privilege escalation: `--nonewprivs`, `--noroot`
- Hardware restrictions: `--nosound`, `--novideo`, `--no3d`, `--nodvd`, `--notv`
- **Custom Profiles**: Support for custom firejail profile files via `replr.worker.firejail.profile` option
- **Custom Profiles**: Support for custom firejail profile files via `repljail.worker.firejail.profile` option
- **Process Management**: Worker executed as `firejail [options] Rscript worker.R <port>`
- **Tests**: `tests/testthat/test-firejail.R` (skipped on CI and non-Linux systems)
- **Demo**: `inst/examples/firejail-demo.R` shows complete usage
Expand All @@ -168,7 +169,7 @@ Main R Process (Controller) ←──nanonext REQ/REP──→ Worker R Process
- Native sandboxing for macOS using Apple's `sandbox-exec` command
- Implemented in `R/worker-wrappers.R` via `MacOSSandboxWorkerWrapper` R6 class
- Availability detection: `is_macos_sandbox_available()` checks for macOS and sandbox-exec command
- Configurable via options: `replr.worker.type = "macos-sandbox"`, `replr.worker.macos.sandbox.profile`
- Configurable via options: `repljail.worker.type = "macos-sandbox"`, `repljail.worker.macos.sandbox.profile`
- **Default Security Profile** (auto-generated using Sandbox Profile Language):
- **Filesystem access**:
- Read: All files (allows R to read system files, libraries, data)
Expand All @@ -188,7 +189,7 @@ Main R Process (Controller) ←──nanonext REQ/REP──→ Worker R Process

### ellmer Integration
- Functions in `R/ellmer-tools.R` provide LLM agent tools for the ellmer package
- Global session registry (`.replr_sessions`) tracks active sessions across tool calls
- Global session registry (`.repljail_sessions`) tracks active sessions across tool calls
- Auto-generated session IDs: `<color>-<animal>-<number>` (e.g., "red-eagle-742")
- All tools return standardized responses: `list(success, message, data, error)`
- Plot handling: Converts base64 plots to temporary PNG files for LLM consumption
Expand Down Expand Up @@ -297,21 +298,20 @@ jj describe -m "Descriptive commit message"
Global options control package behavior:

**General:**
- `replr.debug` (logical): Enable debug logging
- `repljail.debug` (logical): Enable debug logging

**Worker Type Selection:**
- `replr.worker.type` (string): Worker isolation method - one of "native", "docker", "firejail", "macos-sandbox" (default: "native")
- Legacy boolean options (`replr.use.docker`, `replr.use.firejail`, `replr.use.macos.sandbox`) are deprecated but supported with warnings
- `repljail.worker.type` (string): Worker isolation method - one of "native", "docker", "firejail", "macos-sandbox" (default: "native")

**Docker Configuration:**
- `replr.worker.docker.image` (string): Docker image name (default: "replr-worker:latest")
- `replr.worker.docker.memory` (string): Memory limit (default: "512m")
- `replr.worker.docker.cpus` (string): CPU limit (default: "1.0")
- `replr.worker.docker.network.isolation` (logical): Enable isolated Docker networks (default: FALSE)
- `repljail.worker.docker.image` (string): Docker image name (default: "repljail-worker:latest")
- `repljail.worker.docker.memory` (string): Memory limit (default: "512m")
- `repljail.worker.docker.cpus` (string): CPU limit (default: "1.0")
- `repljail.worker.docker.network.isolation` (logical): Enable isolated Docker networks (default: FALSE)

**Firejail Configuration:**
- `replr.worker.firejail.profile` (string): Path to custom firejail profile file (default: NULL)
- `repljail.worker.firejail.profile` (string): Path to custom firejail profile file (default: NULL)

**macOS Sandbox Configuration:**
- `replr.worker.macos.sandbox.profile` (string): Path to custom sandbox profile (.sb) file (default: NULL)
- `repljail.worker.macos.sandbox.profile` (string): Path to custom sandbox profile (.sb) file (default: NULL)
- to memorize : when testing scripts and examples, always use devtools::load_all() and source from within an R session instead of using Rscript
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
Package: replr
Package: repljail
Type: Package
Title: Isolated REPL functionality for R
Version: 0.1.0
Author: Peter Krusche <pkrusche@gmail.com>
Maintainer: Peter Krusche <pkrusche@gmail.com>
Description: Provides isolated REPL functionality for R, allowing users to execute R code in a separate environment.
License: MIT + file LICENSE
License: GPL (>= 3)
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.3
Expand Down
Loading