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/urandom"))
|
||||||
(allow file-ioctl (literal "/dev/dtracehelper"))
|
(allow file-ioctl (literal "/dev/dtracehelper"))
|
||||||
(allow file-ioctl (literal "/dev/tty"))
|
(allow file-ioctl (literal "/dev/tty"))
|
||||||
|
(allow file-ioctl (regex #"^/dev/ttys"))
|
||||||
|
|
||||||
(allow file-ioctl file-read-data file-write-data
|
(allow file-ioctl file-read-data file-write-data
|
||||||
(require-all
|
(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
|
// Network rules
|
||||||
@@ -630,19 +634,13 @@ func GenerateSandboxProfile(params MacOSSandboxParams) string {
|
|||||||
profile.WriteString(rule + "\n")
|
profile.WriteString(rule + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// PTY support
|
// PTY allocation support (creating new pseudo-terminals)
|
||||||
if params.AllowPty {
|
if params.AllowPty {
|
||||||
profile.WriteString(`
|
profile.WriteString(`
|
||||||
; Pseudo-terminal (pty) support
|
; Pseudo-terminal allocation (pty) support
|
||||||
(allow pseudo-tty)
|
(allow pseudo-tty)
|
||||||
(allow file-ioctl
|
(allow file-ioctl (literal "/dev/ptmx"))
|
||||||
(literal "/dev/ptmx")
|
(allow file-read* file-write* (literal "/dev/ptmx"))
|
||||||
(regex #"^/dev/ttys")
|
|
||||||
)
|
|
||||||
(allow file-read* file-write*
|
|
||||||
(literal "/dev/ptmx")
|
|
||||||
(regex #"^/dev/ttys")
|
|
||||||
)
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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.
|
// pf routes all traffic from group _greywall through utun → tun2socks → proxy.
|
||||||
// Using -u #<uid> preserves the user's identity (home dir, SSH keys, etc.)
|
// Using -u #<uid> preserves the user's identity (home dir, SSH keys, etc.)
|
||||||
// while -g _greywall sets the effective GID for pf matching.
|
// 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())
|
uid := fmt.Sprintf("#%d", os.Getuid())
|
||||||
parts = append(parts, "sudo", "-u", uid, "-g", daemonSession.SandboxGroup,
|
proxyEnvs := GenerateProxyEnvVars(cfg.Network.ProxyURL)
|
||||||
"sandbox-exec", "-p", profile, shellPath, "-c", command)
|
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 {
|
} else {
|
||||||
// Non-daemon mode: use proxy env vars for best-effort proxying.
|
// Non-daemon mode: use proxy env vars for best-effort proxying.
|
||||||
proxyEnvs := GenerateProxyEnvVars(cfg.Network.ProxyURL)
|
proxyEnvs := GenerateProxyEnvVars(cfg.Network.ProxyURL)
|
||||||
|
|||||||
@@ -86,6 +86,31 @@ func GenerateProxyEnvVars(proxyURL string) []string {
|
|||||||
return envVars
|
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.
|
// EncodeSandboxedCommand encodes a command for sandbox monitoring.
|
||||||
func EncodeSandboxedCommand(command string) string {
|
func EncodeSandboxedCommand(command string) string {
|
||||||
if len(command) > 100 {
|
if len(command) > 100 {
|
||||||
|
|||||||
Reference in New Issue
Block a user