shared bwrap sandbox, skip for super admin, extend to pi and terminals
- extract buildSandboxPrefix/buildRunuserSuffix into shared sandbox.ts - super admin bypasses bwrap for full host access (claude, pi, terminal) - member pi processes now use bwrap instead of sudo -u - member terminals now use bwrap instead of sudo -u - mount /run for systemd-resolved DNS inside sandbox - pass role through claude spawn params and channel types Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
|
||||
// Resolve paths for sandbox
|
||||
const BUN_DIR = (() => {
|
||||
const result = Bun.spawnSync({ cmd: ['which', 'bun'], stdout: 'pipe', stderr: 'ignore' });
|
||||
const binDir = dirname(result.stdout.toString().trim());
|
||||
return dirname(binDir); // e.g. /home/pastilhas/.bun
|
||||
})();
|
||||
|
||||
const PROJECT_ROOT = resolve(import.meta.dir, '../../..');
|
||||
const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
|
||||
|
||||
// Resolve the OS username for runuser to drop privileges inside the sandbox
|
||||
const OS_USERNAME = (() => {
|
||||
const result = Bun.spawnSync({ cmd: ['id', '-un'], stdout: 'pipe', stderr: 'ignore' });
|
||||
return result.stdout.toString().trim() || 'pastilhas';
|
||||
})();
|
||||
|
||||
// Sandbox mount point for user data (short path avoids intermediate dir traversal issues)
|
||||
export const SANDBOX_DATA = '/data';
|
||||
export const SANDBOX_HOME = `${SANDBOX_DATA}/home`;
|
||||
|
||||
// Build bwrap sandbox prefix for a given user email.
|
||||
// Returns args up to (but not including) the `-- runuser` suffix.
|
||||
// Callers can append extra `--setenv` args before calling `buildRunuserSuffix()`.
|
||||
export function buildSandboxPrefix(email: string): string[] {
|
||||
const userDataDir = join(DATA_PATH, email);
|
||||
const globalToolsDir = join(DATA_PATH, 'tools');
|
||||
const globalExtensionsDir = join(DATA_PATH, 'extensions');
|
||||
|
||||
const args = [
|
||||
'sudo',
|
||||
'bwrap',
|
||||
'--share-net',
|
||||
'--die-with-parent',
|
||||
'--proc',
|
||||
'/proc',
|
||||
'--dev',
|
||||
'/dev',
|
||||
'--perms',
|
||||
'1777',
|
||||
'--tmpfs',
|
||||
'/tmp',
|
||||
// System (read-only)
|
||||
'--ro-bind',
|
||||
'/usr',
|
||||
'/usr',
|
||||
'--ro-bind',
|
||||
'/lib',
|
||||
'/lib',
|
||||
'--ro-bind',
|
||||
'/bin',
|
||||
'/bin',
|
||||
'--ro-bind',
|
||||
'/etc',
|
||||
'/etc',
|
||||
// /run is needed for systemd-resolved DNS (resolv.conf symlink target)
|
||||
'--ro-bind',
|
||||
'/run',
|
||||
'/run',
|
||||
];
|
||||
|
||||
// Optional system paths
|
||||
if (existsSync('/lib64')) args.push('--ro-bind', '/lib64', '/lib64');
|
||||
if (existsSync('/sbin')) args.push('--ro-bind', '/sbin', '/sbin');
|
||||
|
||||
// Bun runtime (e.g. /home/pastilhas/.bun)
|
||||
args.push('--ro-bind', BUN_DIR, BUN_DIR);
|
||||
|
||||
// Project source (for MCP server)
|
||||
args.push('--ro-bind', PROJECT_ROOT, PROJECT_ROOT);
|
||||
|
||||
// Global tools/extensions (read-only, mounted at original paths for MCP config references)
|
||||
if (existsSync(globalToolsDir)) args.push('--ro-bind', globalToolsDir, globalToolsDir);
|
||||
if (existsSync(globalExtensionsDir)) args.push('--ro-bind', globalExtensionsDir, globalExtensionsDir);
|
||||
|
||||
// User data (read-write, mounted at /data to avoid intermediate dir permission issues)
|
||||
args.push('--bind', userDataDir, SANDBOX_DATA);
|
||||
|
||||
// Common env vars inside the sandbox (sudo strips the environment)
|
||||
args.push('--setenv', 'HOME', SANDBOX_HOME);
|
||||
args.push('--setenv', 'PATH', process.env.PATH ?? '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin');
|
||||
|
||||
// Set working directory inside the sandbox
|
||||
args.push('--chdir', SANDBOX_HOME);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
// Build the runuser suffix that drops privileges to the OS user.
|
||||
// Append this after any extra --setenv args.
|
||||
export function buildRunuserSuffix(): string[] {
|
||||
return ['--', 'runuser', '--preserve-environment', '-u', OS_USERNAME, '--'];
|
||||
}
|
||||
Reference in New Issue
Block a user