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:
2026-03-08 00:06:44 +00:00
co-authored by Claude Opus 4.6
parent c38d5b0ea1
commit 9578110e8b
9 changed files with 386 additions and 180 deletions
+14 -2
View File
@@ -4,6 +4,7 @@ import { dirname, join } from 'node:path';
import { getHomeDir } from '@@/data-path';
import { sendPtyCommand, sendPtyCommandAsync, on, isTerminalConnected } from '@@/sidecar-registry';
import type { PtyInitConfig } from '../../sidecar/protocol';
import { buildSandboxPrefix, buildRunuserSuffix, SANDBOX_HOME } from '../../sidecar/sandbox';
type WSData = {
userId: number;
@@ -81,10 +82,21 @@ export const terminalWebsocket = {
mkdirSync(dirname(homeDir), { recursive: true });
mkdirSync(homeDir, { recursive: true });
// Build bwrap command for sandboxed terminal
const prefix = buildSandboxPrefix(email);
prefix.push('--setenv', 'ZDOTDIR', SANDBOX_HOME);
prefix.push('--setenv', 'ZSH', `${SANDBOX_HOME}/.oh-my-zsh`);
prefix.push('--setenv', 'SHELL', '/bin/zsh');
prefix.push('--setenv', 'USER', email);
prefix.push('--setenv', 'LOGNAME', email);
prefix.push('--setenv', 'OFFICER_TERMINAL_USER', email);
prefix.push('--setenv', 'TERM', 'xterm-256color');
const bwrapArgs = [...prefix, ...buildRunuserSuffix(), '/bin/zsh', '-i'];
config = {
sessionId,
username,
cwd: resolveCwd(homeDir, ws.data.cwd),
shell: { command: bwrapArgs[0]!, args: bwrapArgs.slice(1) },
cwd: SANDBOX_HOME,
homeDir,
userLabel: email,
cols: ws.data.cols,