90 lines
3.6 KiB
Go
90 lines
3.6 KiB
Go
|
|
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")
|
||
|
|
}
|
||
|
|
}
|