1package visual
2
3import (
4 "context"
5 "fmt"
6 "io"
7 "os"
8 "os/exec"
9 "syscall"
10)
11
12// execLauncher spawns `<exe> auth-worker` with private pipes: child fd 3 is the
13// worker's input, fd 4 its output. No credential ever appears in argv or env.
14type execLauncher struct{ exe string }
15
16type execWorker struct {
17 cmd *exec.Cmd
18 in, out *os.File
19}
20
21func (l execLauncher) launch(context.Context) (workerProcess, error) {
22 inR, inW, err := os.Pipe()
23 if err != nil {
24 return nil, fmt.Errorf("create auth worker input pipe: %w", err)
25 }
26 outR, outW, err := os.Pipe()
27 if err != nil {
28 inR.Close()
29 inW.Close()
30 return nil, fmt.Errorf("create auth worker output pipe: %w", err)
31 }
32 cmd := exec.Command(l.exe, "auth-worker")
33 cmd.Stderr = os.Stderr
34 cmd.Env = workerEnvironment(os.LookupEnv)
35 cmd.ExtraFiles = []*os.File{inR, outW}
36 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
37 err = cmd.Start()
38 inR.Close()
39 outW.Close()
40 if err != nil {
41 inW.Close()
42 outR.Close()
43 return nil, fmt.Errorf("start auth worker %s: %w", l.exe, err)
44 }
45 return &execWorker{cmd: cmd, in: inW, out: outR}, nil
46}
47
48// workerEnvAllowlist is everything the worker (and the `pass`/gpg it spawns)
49// may inherit from the visual process; nothing else leaks into it.
50var workerEnvAllowlist = []string{
51 "HOME", "PATH", "USER", "LOGNAME", "LANG", "LC_ALL",
52 "XDG_RUNTIME_DIR", "GNUPGHOME", "PASSWORD_STORE_DIR", "GPG_TTY",
53}
54
55// workerEnvironment builds the worker's environment from the allowlist; only
56// variables that are present are passed.
57func workerEnvironment(lookup func(string) (string, bool)) []string {
58 env := make([]string, 0, len(workerEnvAllowlist))
59 for _, key := range workerEnvAllowlist {
60 if v, ok := lookup(key); ok {
61 env = append(env, key+"="+v)
62 }
63 }
64 return env
65}
66
67func (w *execWorker) pipes() (io.WriteCloser, io.ReadCloser) { return w.in, w.out }
68func (w *execWorker) kill() error {
69 return syscall.Kill(-w.cmd.Process.Pid, syscall.SIGKILL)
70}
71func (w *execWorker) wait() error { return w.cmd.Wait() }