From b6d698412557217ab351bd4f01236fdc25ad32fe Mon Sep 17 00:00:00 2001 From: jh-block Date: Tue, 10 Mar 2026 16:27:37 +0100 Subject: [PATCH] fix(security): remove insecure default secret from GOOSE_EXTERNAL_BACKEND (#7783) Co-authored-by: Claude Haiku 4.5 --- Justfile | 1 + crates/goose-server/src/commands/agent.rs | 11 +++++++++-- crates/goose-server/src/tunnel/mod.rs | 15 +++++++++++++-- ui/desktop/src/main.ts | 8 +++++++- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Justfile b/Justfile index db0a304f50..8cb84f197f 100644 --- a/Justfile +++ b/Justfile @@ -144,6 +144,7 @@ debug-ui *alpha: @echo "🚀 Starting goose frontend in external backend mode{{ if alpha == "alpha" { " with alpha features enabled" } else { "" } }}" cd ui/desktop && \ export GOOSE_EXTERNAL_BACKEND=true && \ + export GOOSE_SERVER__SECRET_KEY="${GOOSE_SERVER__SECRET_KEY:-test}" && \ {{ if alpha == "alpha" { "export ALPHA=true &&" } else { "" } }} \ npm ci && \ npm run {{ if alpha == "alpha" { "start-alpha-gui" } else { "start-gui" } }} diff --git a/crates/goose-server/src/commands/agent.rs b/crates/goose-server/src/commands/agent.rs index 6a1a93cc30..c5799e243c 100644 --- a/crates/goose-server/src/commands/agent.rs +++ b/crates/goose-server/src/commands/agent.rs @@ -37,11 +37,18 @@ pub async fn run() -> Result<()> { let settings = configuration::Settings::new()?; - let secret_key = - std::env::var("GOOSE_SERVER__SECRET_KEY").unwrap_or_else(|_| "test".to_string()); + let secret_key = std::env::var("GOOSE_SERVER__SECRET_KEY") + .unwrap_or_else(|_| hex::encode(rand::random::<[u8; 32]>())); let app_state = state::AppState::new(settings.tls).await?; + // Share the server secret with the tunnel manager so it uses the same + // key for forwarded requests, without mutating the process environment. + app_state + .tunnel_manager + .set_server_secret(secret_key.clone()) + .await; + let cors = CorsLayer::new() .allow_origin(Any) .allow_methods(Any) diff --git a/crates/goose-server/src/tunnel/mod.rs b/crates/goose-server/src/tunnel/mod.rs index 32a870eddc..4e75f03045 100644 --- a/crates/goose-server/src/tunnel/mod.rs +++ b/crates/goose-server/src/tunnel/mod.rs @@ -89,6 +89,7 @@ pub struct TunnelManager { watchdog_handle: Arc>>>, lock_file: Arc>>, scheme: String, + server_secret: Arc>>, } impl Default for TunnelManager { @@ -107,6 +108,7 @@ impl TunnelManager { watchdog_handle: Arc::new(RwLock::new(None)), lock_file: Arc::new(std::sync::Mutex::new(None)), scheme: if tls { "https" } else { "http" }.to_string(), + server_secret: Arc::new(RwLock::new(None)), } } @@ -192,6 +194,10 @@ impl TunnelManager { } } + pub async fn set_server_secret(&self, secret: String) { + *self.server_secret.write().await = Some(secret); + } + pub fn set_auto_start(auto_start: bool) -> anyhow::Result<()> { Config::global() .set_param("tunnel_auto_start", auto_start) @@ -213,8 +219,12 @@ impl TunnelManager { async fn start_tunnel_internal(&self) -> anyhow::Result<(TunnelInfo, mpsc::Receiver<()>)> { let server_port = get_server_port()?; let tunnel_secret = Self::get_secret().unwrap_or_else(generate_secret); - let server_secret = - std::env::var("GOOSE_SERVER__SECRET_KEY").unwrap_or_else(|_| "test".to_string()); + let server_secret = self + .server_secret + .read() + .await + .clone() + .expect("server_secret must be set before starting tunnel"); let agent_id = Self::get_agent_id().unwrap_or_else(generate_agent_id); Self::set_secret(&tunnel_secret)?; @@ -317,6 +327,7 @@ impl TunnelManager { watchdog_handle: self.watchdog_handle.clone(), lock_file: self.lock_file.clone(), scheme: self.scheme.clone(), + server_secret: self.server_secret.clone(), } } diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index efa8ba42d1..b18cd76ede 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -511,7 +511,13 @@ const getServerSecret = (settings: Settings): string => { return settings.externalGoosed.secret; } if (process.env.GOOSE_EXTERNAL_BACKEND) { - return 'test'; + if (!process.env.GOOSE_SERVER__SECRET_KEY) { + throw new Error( + 'GOOSE_SERVER__SECRET_KEY must be set when using GOOSE_EXTERNAL_BACKEND. ' + + 'Set it to the same value on both the server and the desktop client.' + ); + } + return process.env.GOOSE_SERVER__SECRET_KEY; } return GENERATED_SECRET; };