diff --git a/CHANGELOG.md b/CHANGELOG.md index d82f3ae1..b773b546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fixed MCP connection to localhost being routed through an active HTTP_PROXY/HTTPS_PROXY; NO_PROXY is now automatically extended with localhost and 127.0.0.1 when a proxy is configured - Fixed terminal scrollbar disappearing when Claude shows a choice/confirmation prompt (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/22) - Fixed Cancel button sending Ctrl+C instead of Esc, making it impossible to exit /usage and similar screens - Fixed Default profile settings not saving in Tools → Options → Profiles (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/31) diff --git a/pom.xml b/pom.xml index 49db55d5..b64609c2 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.nbclaudecodegui netbeans-claude-code-gui - 0.20.8-SNAPSHOT + 0.20.9-SNAPSHOT nbm Netbeans Claude Code GUI NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE. diff --git a/src/main/java/io/github/nbclaudecodegui/process/ClaudeProcess.java b/src/main/java/io/github/nbclaudecodegui/process/ClaudeProcess.java index 1977f2cc..f422fcca 100644 --- a/src/main/java/io/github/nbclaudecodegui/process/ClaudeProcess.java +++ b/src/main/java/io/github/nbclaudecodegui/process/ClaudeProcess.java @@ -136,7 +136,10 @@ public PtyProcess start(String workingDir, ClaudeProfile profile, String extraCl + ", apiKeyHelper=" + (apiKeyHelper ? "SET" : "NOT SET") + ", ANTHROPIC_AUTH_TOKEN=" + (!env.getOrDefault("ANTHROPIC_AUTH_TOKEN", "").isBlank() ? "SET" : "NOT SET") + ", ANTHROPIC_BASE_URL=" + env.getOrDefault("ANTHROPIC_BASE_URL", "(not set)") - + ", CLAUDE_CONFIG_DIR=" + env.getOrDefault("CLAUDE_CONFIG_DIR", "(inherited)")); + + ", CLAUDE_CONFIG_DIR=" + env.getOrDefault("CLAUDE_CONFIG_DIR", "(inherited)") + + ", HTTP_PROXY=" + env.getOrDefault("HTTP_PROXY", env.getOrDefault("http_proxy", "(not set)")) + + ", HTTPS_PROXY=" + env.getOrDefault("HTTPS_PROXY", env.getOrDefault("https_proxy", "(not set)")) + + ", NO_PROXY=" + env.getOrDefault("NO_PROXY", env.getOrDefault("no_proxy", "(not set)"))); List cmd = new ArrayList<>(); cmd.add(executable); @@ -587,9 +590,36 @@ static Map buildEnv(ClaudeProfile profile, java.nio.file.Path pr env.putAll(profile.toEnvVars()); } + // Ensure localhost is excluded from proxy to protect MCP SSE server + ensureLocalhostInNoProxy(env); + return env; } + private static void ensureLocalhostInNoProxy(Map env) { + // Normalize lowercase no_proxy → NO_PROXY (Linux systems may use either) + if (!env.containsKey("NO_PROXY") && env.containsKey("no_proxy")) { + env.put("NO_PROXY", env.get("no_proxy")); + } + + String httpProxy = env.getOrDefault("HTTP_PROXY", env.getOrDefault("http_proxy", "")); + String httpsProxy = env.getOrDefault("HTTPS_PROXY", env.getOrDefault("https_proxy", "")); + if (httpProxy.isBlank() && httpsProxy.isBlank()) return; // no proxy active, nothing to do + + String noProxy = env.getOrDefault("NO_PROXY", ""); + List required = List.of("localhost", "127.0.0.1"); + List missing = required.stream() + .filter(h -> !noProxy.contains(h)) + .collect(java.util.stream.Collectors.toList()); + if (missing.isEmpty()) return; + + String updated = noProxy.isBlank() + ? String.join(",", missing) + : noProxy + "," + String.join(",", missing); + env.put("NO_PROXY", updated); + LOG.fine("Auto-added to NO_PROXY: " + missing + " → NO_PROXY=" + updated); + } + /** * Builds a minimal {@code settings.local.json} from scratch for the given port. * Used as a fallback when the existing file cannot be parsed. diff --git a/src/test/java/io/github/nbclaudecodegui/process/ClaudeProcessProfileTest.java b/src/test/java/io/github/nbclaudecodegui/process/ClaudeProcessProfileTest.java index 1b506b61..72d36126 100644 --- a/src/test/java/io/github/nbclaudecodegui/process/ClaudeProcessProfileTest.java +++ b/src/test/java/io/github/nbclaudecodegui/process/ClaudeProcessProfileTest.java @@ -127,6 +127,49 @@ void buildEnv_systemManagedProxy_noProxyVarsAdded() { } } + // ------------------------------------------------------------------------- + // NO_PROXY auto-injection + // ------------------------------------------------------------------------- + + @Test + void buildEnv_customProxy_noProxyBlank_injectsLocalhost() { + ClaudeProfile p = ClaudeProfile.createNamed("CustomProxyNoNP"); + p.setProxyMode(ProxyMode.CUSTOM); + p.setHttpProxy("http://proxy:3128"); + p.setHttpsProxy(""); + p.setNoProxy(""); + Map env = ClaudeProcess.buildEnv(p, PROFILES_DIR); + String noProxy = env.getOrDefault("NO_PROXY", ""); + assertTrue(noProxy.contains("localhost"), "NO_PROXY should contain localhost, was: " + noProxy); + assertTrue(noProxy.contains("127.0.0.1"), "NO_PROXY should contain 127.0.0.1, was: " + noProxy); + } + + @Test + void buildEnv_customProxy_noProxyHasLocalhost_notDuplicated() { + ClaudeProfile p = ClaudeProfile.createNamed("CustomProxyHasNP"); + p.setProxyMode(ProxyMode.CUSTOM); + p.setHttpProxy("http://proxy:3128"); + p.setHttpsProxy(""); + p.setNoProxy("localhost,127.0.0.1"); + Map env = ClaudeProcess.buildEnv(p, PROFILES_DIR); + String noProxy = env.getOrDefault("NO_PROXY", ""); + // Should not contain duplicates + int localhostCount = noProxy.split("localhost", -1).length - 1; + assertEquals(1, localhostCount, "localhost should appear exactly once in NO_PROXY: " + noProxy); + } + + @Test + void buildEnv_noProxyActive_noLocalhostInjected() { + ClaudeProfile p = ClaudeProfile.createNamed("CustomNoProxyActive"); + p.setProxyMode(ProxyMode.NO_PROXY); + Map env = ClaudeProcess.buildEnv(p, PROFILES_DIR); + // NO_PROXY should be empty (explicitly cleared by NO_PROXY mode) + // and since no proxy is set, ensureLocalhostInNoProxy should not add anything + String noProxy = env.getOrDefault("NO_PROXY", ""); + assertFalse(noProxy.contains("localhost"), + "With no proxy active, localhost should not be injected: " + noProxy); + } + // ------------------------------------------------------------------------- // Extra env vars // -------------------------------------------------------------------------