diff --git a/docs/PROXY_MODE.md b/docs/PROXY_MODE.md index ab04a9c2..6dd00436 100644 --- a/docs/PROXY_MODE.md +++ b/docs/PROXY_MODE.md @@ -22,6 +22,8 @@ awmg proxy \ # Trust the generated CA and point gh at the proxy export GH_HOST=localhost:8443 export NODE_EXTRA_CA_CERTS=/tmp/gh-aw/mcp-logs/proxy-tls/ca.crt +export SSL_CERT_FILE=/tmp/gh-aw/mcp-logs/proxy-tls/ca.crt +export GIT_SSL_CAINFO=/tmp/gh-aw/mcp-logs/proxy-tls/ca.crt gh issue list -R org/repo ``` @@ -167,6 +169,8 @@ docker run --rm -p 8443:8443 \ # Trust the CA cert from the mounted log volume export GH_HOST=localhost:8443 export NODE_EXTRA_CA_CERTS=/tmp/proxy-logs/proxy-tls/ca.crt +export SSL_CERT_FILE=/tmp/proxy-logs/proxy-tls/ca.crt +export GIT_SSL_CAINFO=/tmp/proxy-logs/proxy-tls/ca.crt gh issue list -R org/repo ``` @@ -219,6 +223,8 @@ When `--tls` is enabled, the proxy writes to `--tls-dir` (default: `/pr **gh CLI / Node.js**: ```bash export NODE_EXTRA_CA_CERTS=/tmp/gh-aw/mcp-logs/proxy-tls/ca.crt +export SSL_CERT_FILE=/tmp/gh-aw/mcp-logs/proxy-tls/ca.crt +export GIT_SSL_CAINFO=/tmp/gh-aw/mcp-logs/proxy-tls/ca.crt ``` **System-wide (Ubuntu)**: diff --git a/internal/cmd/proxy.go b/internal/cmd/proxy.go index b24522c4..6c836ac1 100644 --- a/internal/cmd/proxy.go +++ b/internal/cmd/proxy.go @@ -10,6 +10,7 @@ import ( "os" "os/signal" "path/filepath" + "strings" "syscall" "time" @@ -24,6 +25,14 @@ import ( var logProxyCmd = logger.New("cmd:proxy") +var tlsTrustEnvKeys = []string{ + "NODE_EXTRA_CA_CERTS", + "SSL_CERT_FILE", + "GIT_SSL_CAINFO", + "CURL_CA_BUNDLE", + "REQUESTS_CA_BUNDLE", +} + // Proxy subcommand flag variables var ( proxyGuardWasm string @@ -223,6 +232,9 @@ func runProxy(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("failed to generate TLS certificates: %w", err) } + if err := configureTLSTrustEnvironment(tlsCfg.CACertPath); err != nil { + return err + } logger.LogInfo("startup", "TLS certificates generated: ca=%s", tlsCfg.CACertPath) } @@ -268,6 +280,8 @@ func runProxy(cmd *cobra.Command, args []string) error { fmt.Fprintf(os.Stderr, "\nConnect with:\n") fmt.Fprintf(os.Stderr, " export GH_HOST=%s\n", clientAddr(actualAddr)) fmt.Fprintf(os.Stderr, " export NODE_EXTRA_CA_CERTS=%s\n", tlsCfg.CACertPath) + fmt.Fprintf(os.Stderr, " export SSL_CERT_FILE=%s\n", tlsCfg.CACertPath) + fmt.Fprintf(os.Stderr, " export GIT_SSL_CAINFO=%s\n", tlsCfg.CACertPath) fmt.Fprintf(os.Stderr, " gh issue list -R org/repo\n\n") } else { fmt.Fprintf(os.Stderr, "\nConnect with:\n") @@ -302,3 +316,16 @@ func clientAddr(addr string) string { } return addr } + +func configureTLSTrustEnvironment(caCertPath string) error { + if strings.ContainsAny(caCertPath, "\r\n") { + return fmt.Errorf("invalid TLS CA cert path contains newline") + } + + for _, key := range tlsTrustEnvKeys { + if err := os.Setenv(key, caCertPath); err != nil { + return fmt.Errorf("failed to set %s: %w", key, err) + } + } + return nil +} diff --git a/internal/cmd/proxy_test.go b/internal/cmd/proxy_test.go index 7a837941..b9f26822 100644 --- a/internal/cmd/proxy_test.go +++ b/internal/cmd/proxy_test.go @@ -409,3 +409,49 @@ func TestClientAddr(t *testing.T) { }) } } + +func TestConfigureTLSTrustEnvironment(t *testing.T) { + caPath := "/tmp/proxy-tls/ca.crt" + + t.Run("sets trust environment variables in process", func(t *testing.T) { + assert := assert.New(t) + t.Setenv("GITHUB_ENV", "") + for _, key := range tlsTrustEnvKeys { + t.Setenv(key, "") + } + + err := configureTLSTrustEnvironment(caPath) + require.NoError(t, err) + + for _, key := range tlsTrustEnvKeys { + assert.Equal(caPath, os.Getenv(key), "expected %s to be set", key) + } + }) + + t.Run("does not rely on GITHUB_ENV", func(t *testing.T) { + assert := assert.New(t) + githubEnvFile := t.TempDir() + "/github_env" + const original = "UNCHANGED=1\n" + require.NoError(t, os.WriteFile(githubEnvFile, []byte(original), 0o644)) + t.Setenv("GITHUB_ENV", githubEnvFile) + for _, key := range tlsTrustEnvKeys { + t.Setenv(key, "") + } + + require.NoError(t, configureTLSTrustEnvironment(caPath)) + + for _, key := range tlsTrustEnvKeys { + assert.Equal(caPath, os.Getenv(key), "expected %s to be set", key) + } + + content, err := os.ReadFile(githubEnvFile) + require.NoError(t, err) + assert.Equal(original, string(content)) + }) + + t.Run("rejects CA cert path with newline", func(t *testing.T) { + err := configureTLSTrustEnvironment("/tmp/ca.crt\nMALICIOUS=1") + require.Error(t, err) + assert.Contains(t, err.Error(), "contains newline") + }) +}