diff --git a/cmd/byre/clientexec.go b/cmd/byre/clientexec.go index ad60816..b4fe141 100644 --- a/cmd/byre/clientexec.go +++ b/cmd/byre/clientexec.go @@ -6,6 +6,8 @@ import ( "flag" "fmt" "net/http" + "net/netip" + "net/url" "os" "strings" "time" @@ -50,7 +52,9 @@ func clientExecCmd(args []string) { } hr.Header.Set("Authorization", "Bearer "+token) hr.Header.Set("Content-Type", "application/json") - hc := &http.Client{Timeout: time.Duration(*timeout)*time.Second + execGrace} + tr := http.DefaultTransport.(*http.Transport).Clone() + tr.Proxy = apiProxy + hc := &http.Client{Transport: tr, Timeout: time.Duration(*timeout)*time.Second + execGrace} resp, err := hc.Do(hr) if err != nil { execFail(execUnavailable, err.Error()) @@ -82,6 +86,50 @@ func clientExecCmd(args []string) { os.Exit(res.ExitCode) } +// http.ProxyFromEnvironment never proxies loopback, but a sandboxed shell can +// reach byre's loopback API only through its proxy. +func apiProxy(r *http.Request) (*url.URL, error) { + if os.Getenv("BYRE_NO_PROXY") == "1" { + return nil, nil + } + p := os.Getenv("HTTP_PROXY") + if p == "" { + p = os.Getenv("http_proxy") + } + if p == "" || noProxy(r.URL) { + return nil, nil + } + if !strings.Contains(p, "://") { + p = "http://" + p + } + return url.Parse(p) +} + +func noProxy(u *url.URL) bool { + list := os.Getenv("NO_PROXY") + if list == "" { + list = os.Getenv("no_proxy") + } + host := u.Hostname() + ip, ipErr := netip.ParseAddr(host) + for _, e := range strings.Split(list, ",") { + e = strings.TrimSpace(e) + if e == "" { + continue + } + if pfx, err := netip.ParsePrefix(e); err == nil { + if ipErr == nil && pfx.Contains(ip) { + return true + } + continue + } + if e == "*" || e == u.Host || e == host || strings.HasSuffix(host, "."+strings.TrimPrefix(e, ".")) { + return true + } + } + return false +} + func execFail(code int, msg string) { fmt.Fprintln(os.Stderr, "byre client-exec: "+msg) os.Exit(code) diff --git a/internal/agents/kb/client-exec.md b/internal/agents/kb/client-exec.md index cbc8cfc..5c4735b 100644 --- a/internal/agents/kb/client-exec.md +++ b/internal/agents/kb/client-exec.md @@ -26,6 +26,10 @@ BYRE_TOKEN=$(<"$HOME/Library/Application Support/byre/loopback-token") byre clie byre logs each call (client, command, exit status, duration), never its output. +## Sandboxed shells + +client-exec calls byre's API at `http://127.0.0.1:8270`. When `HTTP_PROXY` (or `http_proxy`) is set it sends that call through the proxy, loopback included, unless `NO_PROXY` lists the address; a sandbox that denies direct loopback connections then needs only to allow `127.0.0.1:8270` through its proxy. `BYRE_NO_PROXY=1` connects directly whatever the proxy variables say. + ## Screen lock `byre screen` prints `unlocked`, `locked` or `no-session` (no GUI login for this user) for the Mac it runs on, and exits 0. Check the server Mac with `byre screen` and the client with: