package cursor import ( "context" "crypto/tls" "fmt" "net/http" "net/http/httptest" "net/url" "os" "os/exec" "path/filepath" "strconv" "strings" "testing" "time" "cursor/internal/certs" ) func TestCLIHTTP2TrustSNIAndUnrelatedTLSRejection(t *testing.T) { if _, err := exec.LookPath("node"); err != nil { t.Fatal("Node.js is required for CLI TLS integration tests") } dir := filepath.Join(t.TempDir(), "中文 space ' test") if err := os.MkdirAll(dir, 0700); err != nil { t.Fatal(err) } manager, err := certs.LoadOrCreateLocalCA(dir) if err != nil { t.Fatal(err) } server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.ProtoMajor != 2 { t.Error("not HTTP/2") } _, _ = w.Write([]byte("secure-http2")) })) server.EnableHTTP2 = true server.TLS = &tls.Config{GetCertificate: func(h *tls.ClientHelloInfo) (*tls.Certificate, error) { return manager.CertificateForServerName(h.ServerName) }} server.StartTLS() defer server.Close() u, _ := url.Parse(server.URL) preload := filepath.Join(dir, cliPreloadFilename) if err = os.WriteFile(preload, []byte(cliPreloadScript), 0600); err != nil { t.Fatal(err) } good := `const h=require('node:http2');const s=h.connect('https://api2.cursor.sh');s.on('error',e=>{console.error(e.code);process.exitCode=1});const r=s.request({':path':'/'});let b='';r.on('data',d=>b+=d);r.on('end',()=>{if(b!=='secure-http2')process.exitCode=2;s.close()});r.end();` bad := fmt.Sprintf(`const t=require('node:tls');const s=t.connect({host:'127.0.0.1',port:%s},()=>{console.error('accepted invalid host');s.destroy();process.exitCode=2});s.on('error',()=>process.exit(0));`, u.Port()) for _, script := range []string{good, bad} { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) cmd := exec.CommandContext(ctx, "node", "-e", script) cmd.Env = append(os.Environ(), "CLI_HTTPS_PORT="+u.Port(), "NODE_EXTRA_CA_CERTS="+filepath.Join(dir, "ca.crt"), "NODE_OPTIONS=--require="+strconv.Quote(preload), "NODE_TLS_REJECT_UNAUTHORIZED=") out, err := cmd.CombinedOutput() cancel() if err != nil { t.Fatalf("CLI integration: %v %s", err, out) } } wrong, err := certs.LoadOrCreateLocalCA(t.TempDir()) if err != nil { t.Fatal(err) } wrongPath := filepath.Join(dir, "wrong.crt") if err = os.WriteFile(wrongPath, wrong.CACertPEM(), 0600); err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "node", "-e", `const h=require('node:http2');const s=h.connect('https://api2.cursor.sh');s.on('connect',()=>{s.close();process.exitCode=2});s.on('error',()=>process.exit(0));`) cmd.Env = append(os.Environ(), "CLI_HTTPS_PORT="+u.Port(), "NODE_EXTRA_CA_CERTS="+wrongPath, "NODE_OPTIONS=--require="+strconv.Quote(preload), "NODE_TLS_REJECT_UNAUTHORIZED=") if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("wrong-CA rejection: %v %s", err, out) } } func TestWrapperQuotesPathsAndDoesNotDisableTLS(t *testing.T) { path := `C:\用户 space\O'Brien\tls-redirect.cjs` ps := buildWindowsLauncher("39092", `C:\用户 space\ca.crt`, "test-token", `C:\用户 space\agent.cmd`, path) if strings.Contains(ps, "NODE_TLS_REJECT_UNAUTHORIZED=0") || !strings.Contains(ps, "O''Brien") || !strings.Contains(ps, "--require=") { t.Fatal("unsafe PowerShell launcher") } sh := buildUnixWrapper("39092", "/tmp/user ' space/ca.crt", "test-token", "/tmp/bin ' /agent", "/tmp/user ' space/preload.cjs") if strings.Contains(sh, "REJECT_UNAUTHORIZED=0") || !strings.Contains(sh, `'"'"'`) { t.Fatal("unsafe shell wrapper") } }