fix(sandbox): bind parent dir for globstar allow_write on bwrap (#321)

* fix(sandbox): bind parent dir for globstar allow_write on bwrap

Fine-grained per-path mounts under read-only project binds broke pip
install into in-project .venv directories. Always mount the parent tree
for ** write rules instead.

Fixes #315

* test(sandbox): tighten globstar bind assertions and ensure ~/.npm exists for e2e

Strengthen TestBubblewrapAllowWriteGlobstarBindsParentOnly to verify the
parent dir is writably bound and the child path is read-only bound, not
just substring presence. Pre-create ~/.npm in the e2e harness so
bubblewrap --bind-try does not skip the npm cache dir on fresh runners.

* switch pnpm to /tmp in sandbox e2e

* test(sandbox): update glob ** test for parent-bind semantics

Globstar allow_write now binds the parent dir only (e2e740d), so the
test should assert the parent is writably bound and child subdirs are
not individually bound, instead of substring-matching subdir names.

* fix(sandbox): bind correct base dir for in-pattern globstar allow_write

Globstar allow_write previously used extractGlobParentDir, which walks past
the first ** and yields the wrong root for patterns like /a/b/**/d/**/e.
Introduce extractGlobstarWriteBaseDir, which takes the prefix before the
first /**, and use it in processWriteRule. Also dedup the coarse-fallback
parent-bind loop to mirror the read-rule fallback.
This commit is contained in:
Sahil Bansal
2026-06-07 10:03:23 +05:30
committed by GitHub
parent f3e00a7f6e
commit 872c5d663c
4 changed files with 165 additions and 13 deletions
+25 -3
View File
@@ -47,15 +47,37 @@ function cleanup(dir) {
}
}
// npm-restrictive allow_write includes /tmp/**. Use cache/store paths under /tmp so
// bubblewrap can bind /tmp read-write without pre-creating ~/.npm or ~/.cache/pnpm.
function pmEnv(pm) {
const base = path.join(os.tmpdir(), 'pmg-e2e');
fs.mkdirSync(base, { recursive: true });
const env = { ...process.env };
env.npm_config_cache = path.join(base, 'npm-cache');
fs.mkdirSync(env.npm_config_cache, { recursive: true });
if (pm === 'pnpm') {
env.PNPM_HOME = path.join(base, 'pnpm-home');
env.npm_config_store_dir = path.join(base, 'pnpm-store');
fs.mkdirSync(env.PNPM_HOME, { recursive: true });
fs.mkdirSync(env.npm_config_store_dir, { recursive: true });
env.PATH = `${env.PNPM_HOME}${path.delimiter}${env.PATH}`;
}
return env;
}
function testPackageManager(pm) {
const testDir = createTempDir(`pmg-e2e-${pm}-`);
const env = pmEnv(pm);
console.log(`\n Test directory: ${testDir}`);
try {
// Initialize project
test(`${pm}: Initialize project`, () => {
const initCmd = pm === 'npm' ? 'npm init -y' : 'pnpm init';
const result = exec(initCmd, { cwd: testDir });
const result = exec(initCmd, { cwd: testDir, env });
if (!result.success) {
console.log(` ❌ FAIL: ${result.error}`);
return false;
@@ -77,7 +99,7 @@ function testPackageManager(pm) {
const addCmd = pm === 'npm'
? `npm install ${depsList}`
: `pnpm add ${depsList}`;
const result = exec(addCmd, { cwd: testDir });
const result = exec(addCmd, { cwd: testDir, env });
if (!result.success) {
console.log(` ❌ FAIL: ${result.error}`);
return false;
@@ -122,7 +144,7 @@ function testPackageManager(pm) {
// Reinstall
const installCmd = pm === 'npm' ? 'npm install' : 'pnpm install';
const result = exec(installCmd, { cwd: testDir });
const result = exec(installCmd, { cwd: testDir, env });
if (!result.success) {
console.log(` ❌ FAIL: ${result.error}`);
return false;