Skip to content

feature/sandbox system library#402

Open
gwangjinkim wants to merge 5 commits intoA2-ai:mainfrom
gwangjinkim:feature/sandbox-system-library
Open

feature/sandbox system library#402
gwangjinkim wants to merge 5 commits intoA2-ai:mainfrom
gwangjinkim:feature/sandbox-system-library

Conversation

@gwangjinkim
Copy link
Copy Markdown

@gwangjinkim gwangjinkim commented Jan 28, 2026

Problem

Base R always appends .Library to .libPaths(). On some systems (notably macOS, but also Linux, and likely also Windows) the system library can contain user-installed packages, which then leak into rv projects and break isolation/determinism/reproducibility of the rv project. I discussed this in the closed issue #400 in detail. I now created #403 to re-explain it clearer.

Prior art: renv system-library sandbox

rv’s approach here follows the same isolation principle used by renv (tidyverse): sandbox the system library so that only packages with Priority: base or Priority: recommended are visible via .Library, preventing leakage of user-installed packages from the system library to the rv project.

References:

What this PR does

This PR adds renv-style system library sandboxing to the generated rv/scripts/activate.R:

  • Creates a per-project sandbox at rv/sandbox/<rver>/<arch>
  • Populates it with only packages whose DESCRIPTION has Priority: base or Priority: recommended
  • Repoints the locked binding .Library to the sandbox before calling .libPaths()
  • This preserves base R behavior (base/recommended always available) while preventing leakage of non-base packages from the system library
  • Notes in README.md and CHANGELOG.md are added but require your adjustment.
  • No tests are added, since the activate.R template in src/consts.rs is the ground truth.

Startup safety

Implementation avoids installed.packages() during startup (which depends on utils) and instead scans DESCRIPTION files using base-only functions, so R can still load default packages normally.

Opt-out

Set RV_SANDBOX=0 to disable sandboxing for a session.

How to verify

  1. rv init in a fresh directory
  2. Start R
  3. Check:
.libPaths()
.Library

Expected: .Library points inside rv/sandbox/... and .libPaths() ends with that sandbox.

Opt-out check:

RV_SANDBOX=0 R

Expected: .Library is the normal system library again.

@gwangjinkim gwangjinkim changed the title Feature/sandbox system library feature/sandbox system library Jan 28, 2026
@gwangjinkim
Copy link
Copy Markdown
Author

gwangjinkim commented Jan 28, 2026

For testing, the best is to do:

cd /path/to/rv  # enter current git folder
cargo test
cargo build
cargo build --bin rv --features="cli"    # generates binary in target/debug/ => target/debug/rv

# create an example project folder using this new `rv` binary
/path/to/rv/target/debug/rv init /tmp/rv-sandbox-test
# enter test repo/project folder
cd /tmp/rv-sandbox-test
R

R starts then with:

R version 4.4.3 (2025-02-28) -- "Trophy Case"
Copyright (C) 2025 The R Foundation for Statistical Computing
Platform: aarch64-apple-darwin20

R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.

  Natural language support but running in an English locale

R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.

Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.

rv repositories active!
repositories: 
  CRAN: https://cloud.r-project.org/

rv system library sandbox active (base+recommended only):
  /private/tmp/rv-sandbox-test/rv/sandbox/4.4/arm64
opt-out: set RV_SANDBOX=0

rv libpaths active!
library paths: 
  /private/tmp/rv-sandbox-test/rv/library/4.4/arm64
  /private/tmp/rv-sandbox-test/rv/sandbox/4.4/arm64
> .libPaths()
  /private/tmp/rv-sandbox-test/rv/library/4.4/arm64
  /private/tmp/rv-sandbox-test/rv/sandbox/4.4/arm64
> .Library
[1] "/private/tmp/rv-sandbox-test/rv/sandbox/4.4/arm64"

@dpastoor
Copy link
Copy Markdown
Member

Thank you very much for your willingness to contribute!

I will do a deeper review of this soon, but one conceptual adjustment that I would like to start with is I would like to make the sandbox opt in. Essentially the sandbox is only needed when working in "polluted" environments where base/recommended packages are cohabitating with userland packages and we need to isolate that. With your miniconda example tahts what you hit.

On the flip side, the renv sandbox causes some issues around performance, opt-in behavior, where to put the sandbox, filesystem boundaries, etc. So what I'd like to do is we work to get this implemented, but it should instead behave where either

  1. RV_SANDBOX_ENABLE is set
  2. in the rproject.toml we should have a sandbox: bool, with a default value of false, so someone can set it once in the config.

the config setting should take precedence over the environment variable.

One thing we also need to test against, is we should be addressing that sandbox in the rv R process as well by making sure it also understands what to do - this is something that isn't in scope with renv since renv runs in the R process. So we need rv to help "activate" the environment properly, it also needs to itself provide isolation during package installs to make sure those R processes also understand what and where to point to. This turns into us now modifying forceably the R_LIBS_X variables to not be a single path, the current rv library, but also the sandbox if its enabled so it'd be like "path/to/rv/lib:path/to/sandbox".

I also want to explicitly call out your putting in detailed steps you've been using for testing - very much appreciated :-)

Would you mind seeing how much of this makes sense/you can implement and we can start helping as well to work from the base you've put together.

@gwangjinkim
Copy link
Copy Markdown
Author

@dpastoor Thank you for the answer!

Essentially the sandbox is only needed when working in "polluted" environments where base/recommended packages are cohabitating with userland packages and we need to isolate that.

I see this is true, when R was installed globally using sudo.
Also in conda environments, where no R is installed we have 2 locations .libPaths() where .Library is the second and protected from writing (file.access(.Library, 2) returns -1 => not writable).
But as soon as we install R into a conda environment, .libPaths() returns only 1 location -> .Library, and file.access(.Library, 2) returns 0 => writeable, thus everything installed into the conda environment gets installed into it.

Thus, as soon as the user uses any R in a conda environment, he eventually would wish a sandboxing.
(except he wants to mix with conda-installed packages). I guess however the conda packages won't be part of rv.lock in any time, isn't it? So, not good for reproducibility.
But sometimes, there are very difficult to install packages in R (which require very specific versions of systemlibraries or other programming language binaries (and packages?) and for them conda R packages are nice to have as an option).

  1. RV_SANDBOX_ENABLE is set
  2. in the rproject.toml we should have a sandbox: bool, with a default value of false, so someone can set it once in the config.

the config setting should take precedence over the environment variable.

This should be doable. The config-first setting I totally agree.

One thing we also need to test against, is we should be addressing that sandbox in the rv R process as well by making sure it also understands what to do - this is something that isn't in scope with renv since renv runs in the R process. So we need rv to help "activate" the environment properly, it also needs to itself provide isolation during package installs to make sure those R processes also understand what and where to point to. This turns into us now modifying forceably the R_LIBS_X variables to not be a single path, the current rv library, but also the sandbox if its enabled so it'd be like "path/to/rv/lib:path/to/sandbox".

The activate.R script runs only when the session starts. So we are inside R already, when we deal with .Library or sandboxing. So this is a matter at the start of an R session. All R processes looking up core packages will use .Library I guess.

@gwangjinkim
Copy link
Copy Markdown
Author

Now I implemented RV_SANDBOX_ENABLE and the sandbox = true or sandbox = false or lack of the sandbox in [Project] or the rproject.toml file.

Important is that after every change rv init has to be executed - otherwise neither the environment variable nor the rproject.toml entry of the sandbox = config variable will take place.

The testing is not so easy - many manual steps:

# cd to the `rv` folder with the changes given here.
cd /path/to/rv

# compile it
cargo build --bin rv --features="cli"

# this generates in the build folder the `rv` binary in/as ./target/debug/rv.
# for this session extend the PATH variable so that this local `rv` gets recognized first:
export PATH=$(pwd)/target/debug:$PATH

# from now on `rv` uses this freshly built `rv`.

# now go to a test folder or generate it using `rv init`
rv init /tmp/rv-test
cd /tmp/rv-test

# now you can change either
RV_SANDBOX_ENABLE=1   # or 0
# or  change the config file
nano /tmp/rv-test/rproject.toml

by either entering under [Project]
`sandbox = true`
or
`sandbox = false`
or leave it out.

# after every chnge - NEVER forget to:
rv init
# from inside the test project folder.

R
# now you can start R
# the greeting message already shows you whether the sandbox is active or not.

Forgetting rv init after a change happens very frequently.
So take care!

.libPaths() and .Library get at least one path, which contains ../sandbox/.

Actually, instead of rv init, a rv sync should do it, too but it doesn't yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants