fix: preserve terminal env vars through sudo in macOS daemon mode
sudo resets the environment, stripping TERM, COLORTERM, COLUMNS, LINES, and other terminal-related variables that TUI apps need to render. This caused TUI apps like opencode to show a blank screen in daemon mode. Fix by injecting terminal and proxy env vars via `env` after `sudo` in the daemon mode command pipeline. Also move PTY device ioctl/read/write rules into the base sandbox profile so inherited terminals work without requiring AllowPty.
This commit is contained in:
@@ -556,6 +556,7 @@ func GenerateSandboxProfile(params MacOSSandboxParams) string {
|
||||
(allow file-ioctl (literal "/dev/urandom"))
|
||||
(allow file-ioctl (literal "/dev/dtracehelper"))
|
||||
(allow file-ioctl (literal "/dev/tty"))
|
||||
(allow file-ioctl (regex #"^/dev/ttys"))
|
||||
|
||||
(allow file-ioctl file-read-data file-write-data
|
||||
(require-all
|
||||
@@ -564,6 +565,9 @@ func GenerateSandboxProfile(params MacOSSandboxParams) string {
|
||||
)
|
||||
)
|
||||
|
||||
; Inherited terminal access (TUI apps need read/write on the actual PTY device)
|
||||
(allow file-read-data file-write-data (regex #"^/dev/ttys"))
|
||||
|
||||
`)
|
||||
|
||||
// Network rules
|
||||
@@ -630,19 +634,13 @@ func GenerateSandboxProfile(params MacOSSandboxParams) string {
|
||||
profile.WriteString(rule + "\n")
|
||||
}
|
||||
|
||||
// PTY support
|
||||
// PTY allocation support (creating new pseudo-terminals)
|
||||
if params.AllowPty {
|
||||
profile.WriteString(`
|
||||
; Pseudo-terminal (pty) support
|
||||
; Pseudo-terminal allocation (pty) support
|
||||
(allow pseudo-tty)
|
||||
(allow file-ioctl
|
||||
(literal "/dev/ptmx")
|
||||
(regex #"^/dev/ttys")
|
||||
)
|
||||
(allow file-read* file-write*
|
||||
(literal "/dev/ptmx")
|
||||
(regex #"^/dev/ttys")
|
||||
)
|
||||
(allow file-ioctl (literal "/dev/ptmx"))
|
||||
(allow file-read* file-write* (literal "/dev/ptmx"))
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -738,9 +736,16 @@ func WrapCommandMacOS(cfg *config.Config, command string, exposedPorts []int, da
|
||||
// pf routes all traffic from group _greywall through utun → tun2socks → proxy.
|
||||
// Using -u #<uid> preserves the user's identity (home dir, SSH keys, etc.)
|
||||
// while -g _greywall sets the effective GID for pf matching.
|
||||
//
|
||||
// sudo resets the environment, so we use `env` after sudo to re-inject
|
||||
// terminal vars (TERM, COLORTERM, etc.) needed for TUI apps and proxy vars.
|
||||
uid := fmt.Sprintf("#%d", os.Getuid())
|
||||
parts = append(parts, "sudo", "-u", uid, "-g", daemonSession.SandboxGroup,
|
||||
"sandbox-exec", "-p", profile, shellPath, "-c", command)
|
||||
proxyEnvs := GenerateProxyEnvVars(cfg.Network.ProxyURL)
|
||||
termEnvs := getTerminalEnvVars()
|
||||
parts = append(parts, "sudo", "-u", uid, "-g", daemonSession.SandboxGroup, "env")
|
||||
parts = append(parts, proxyEnvs...)
|
||||
parts = append(parts, termEnvs...)
|
||||
parts = append(parts, "sandbox-exec", "-p", profile, shellPath, "-c", command)
|
||||
} else {
|
||||
// Non-daemon mode: use proxy env vars for best-effort proxying.
|
||||
proxyEnvs := GenerateProxyEnvVars(cfg.Network.ProxyURL)
|
||||
|
||||
@@ -86,6 +86,31 @@ func GenerateProxyEnvVars(proxyURL string) []string {
|
||||
return envVars
|
||||
}
|
||||
|
||||
// getTerminalEnvVars returns KEY=VALUE entries for terminal-related environment
|
||||
// variables that are set in the current process. These must be re-injected after
|
||||
// sudo (which resets the environment) so that TUI apps can detect terminal
|
||||
// capabilities, size, and color support.
|
||||
func getTerminalEnvVars() []string {
|
||||
termVars := []string{
|
||||
"TERM",
|
||||
"COLORTERM",
|
||||
"COLUMNS",
|
||||
"LINES",
|
||||
"TERMINFO",
|
||||
"TERMINFO_DIRS",
|
||||
"LANG",
|
||||
"LC_ALL",
|
||||
"LC_CTYPE",
|
||||
}
|
||||
var envs []string
|
||||
for _, key := range termVars {
|
||||
if val := os.Getenv(key); val != "" {
|
||||
envs = append(envs, key+"="+val)
|
||||
}
|
||||
}
|
||||
return envs
|
||||
}
|
||||
|
||||
// EncodeSandboxedCommand encodes a command for sandbox monitoring.
|
||||
func EncodeSandboxedCommand(command string) string {
|
||||
if len(command) > 100 {
|
||||
|
||||
Reference in New Issue
Block a user