coder 1
2026-09-17 13:43:28 -04:00
committed by bb-expert
parent 1b21fa03c1
commit 31ea152d74
6 changed files with 232 additions and 48 deletions

View File

@@ -33,6 +33,7 @@ base64 = "0.22"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tauri-build = { version = "2", features = [] }
url = "2"
[target.'cfg(unix)'.dependencies]
libc = "0.2"

View File

@@ -1,6 +1,7 @@
// Shared schema, included from the same source the runtime command parses with,
// so the build-time validation below and the runtime parse cannot drift.
include!("src/commands/reconnect_hook_config.rs");
include!("src/commands/enterprise_relay_url.rs");
// Same source of truth the runtime filters with, so a baked build env cannot
// carry a reserved key the runtime believes it already rejected.
include!("src/managed_agents/reserved_env_keys.rs");
@@ -128,24 +129,7 @@ fn main() {
if let Ok(relays) = std::env::var("BUZZ_BUILD_ENTERPRISE_AUTH_RELAYS") {
let trimmed = relays.trim();
if trimmed.is_empty() {
panic!("BUZZ_BUILD_ENTERPRISE_AUTH_RELAYS must not be empty when set");
}
for relay in trimmed
.split(',')
.map(str::trim)
.filter(|value| !value.is_empty())
{
if !(relay.starts_with("wss://")
|| relay.starts_with("ws://")
|| relay.starts_with("https://")
|| relay.starts_with("http://"))
{
panic!(
"BUZZ_BUILD_ENTERPRISE_AUTH_RELAYS entries must be relay URLs, got {relay:?}"
);
}
}
parse_enterprise_relay_allowlist(trimmed).unwrap_or_else(|error| panic!("{error}"));
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_ENTERPRISE_AUTH_RELAYS={trimmed}");
}

View File

@@ -1,13 +1,11 @@
use std::time::Duration;
use serde::Serialize;
use tauri::State;
use url::Url;
use crate::{
app_state::AppState,
relay::{classify_request_error, parse_json_response, relay_error_message},
};
use serde::Serialize;
use tauri::State;
#[derive(Debug, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase", tag = "status")]
@@ -141,40 +139,17 @@ fn trusted_enterprise_relays(raw: Option<&'static str>) -> Result<Vec<String>, S
let Some(raw) = raw.map(str::trim).filter(|value| !value.is_empty()) else {
return Ok(Vec::new());
};
raw.split(',')
.map(str::trim)
.filter(|value| !value.is_empty())
.map(canonical_relay_url)
.collect()
super::enterprise_relay_url::parse_enterprise_relay_allowlist(raw)
}
fn relay_matches_any_trusted(relay_url: &str, trusted: &[String]) -> Result<bool, String> {
if trusted.is_empty() {
return Ok(false);
}
let relay = canonical_relay_url(relay_url)?;
let relay = super::enterprise_relay_url::canonical_enterprise_relay_url(relay_url)?;
Ok(trusted.iter().any(|trusted| trusted == &relay))
}
fn canonical_relay_url(raw: &str) -> Result<String, String> {
let mut url = Url::parse(raw.trim()).map_err(|error| format!("invalid relay URL: {error}"))?;
let scheme = match url.scheme() {
"ws" | "http" => "ws",
"wss" | "https" => "wss",
other => return Err(format!("unsupported relay URL scheme: {other}")),
};
url.set_scheme(scheme)
.map_err(|_| "could not normalize relay URL scheme".to_owned())?;
if url.host_str().is_none() {
return Err("relay URL must include a host".to_owned());
}
url.set_fragment(None);
url.set_query(None);
let path = url.path().trim_end_matches('/').to_owned();
url.set_path(&path);
Ok(url.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
@@ -205,6 +180,59 @@ mod tests {
);
}
#[test]
fn default_ports_are_canonicalized_for_trusted_matching() {
assert_eq!(
evaluate_enterprise_login_gate(
"https://buzz.block.builderlab.xyz/",
&discovery_document(),
Some("wss://buzz.block.builderlab.xyz:443"),
)
.unwrap(),
EnterpriseLoginGateStatus::Required,
);
assert_eq!(
evaluate_enterprise_login_gate(
"http://localhost/",
&discovery_document(),
Some("ws://localhost:80"),
)
.unwrap(),
EnterpriseLoginGateStatus::Required,
);
}
#[test]
fn trusted_allowlist_rejects_empty_entries() {
let error = trusted_enterprise_relays(Some("wss://relay.example,"))
.expect_err("empty entries must fail closed");
assert!(error.contains("entry 2 must not be empty"));
}
#[test]
fn trusted_allowlist_rejects_userinfo_query_and_fragment() {
for relay in [
"wss://user@relay.example",
"wss://relay.example?env=prod",
"wss://relay.example#prod",
] {
assert!(
trusted_enterprise_relays(Some(relay)).is_err(),
"{relay} should be rejected",
);
}
}
#[test]
fn trusted_allowlist_rejects_missing_host_and_unsupported_scheme() {
for relay in ["wss://", "file://relay.example"] {
assert!(
trusted_enterprise_relays(Some(relay)).is_err(),
"{relay} should be rejected",
);
}
}
#[test]
fn ordinary_relay_without_discovery_is_not_required() {
assert_eq!(

View File

@@ -0,0 +1,56 @@
use url::Url;
pub(crate) fn parse_enterprise_relay_allowlist(raw: &str) -> Result<Vec<String>, String> {
let trimmed = raw.trim();
if trimmed.is_empty() {
return Err("BUZZ_BUILD_ENTERPRISE_AUTH_RELAYS must not be empty when set".to_owned());
}
trimmed
.split(',')
.enumerate()
.map(|(index, relay)| {
let relay = relay.trim();
if relay.is_empty() {
return Err(format!(
"BUZZ_BUILD_ENTERPRISE_AUTH_RELAYS entry {} must not be empty",
index + 1
));
}
canonical_enterprise_relay_url(relay)
})
.collect()
}
pub(crate) fn canonical_enterprise_relay_url(raw: &str) -> Result<String, String> {
let mut url = Url::parse(raw.trim()).map_err(|error| format!("invalid relay URL: {error}"))?;
let scheme = match url.scheme() {
"ws" | "http" => "ws",
"wss" | "https" => "wss",
other => return Err(format!("unsupported relay URL scheme: {other}")),
};
url.set_scheme(scheme)
.map_err(|_| "could not normalize relay URL scheme".to_owned())?;
if url.host_str().is_none() {
return Err("relay URL must include a host".to_owned());
}
if !url.username().is_empty() || url.password().is_some() {
return Err("relay URL must not include userinfo".to_owned());
}
if url.query().is_some() {
return Err("relay URL must not include a query string".to_owned());
}
if url.fragment().is_some() {
return Err("relay URL must not include a fragment".to_owned());
}
if matches!((scheme, url.port()), ("ws", Some(80)) | ("wss", Some(443))) {
url.set_port(None)
.map_err(|_| "could not normalize relay URL port".to_owned())?;
}
let path = url.path().trim_end_matches('/').to_owned();
url.set_path(&path);
Ok(url.to_string())
}

View File

@@ -22,6 +22,7 @@ mod clipboard;
mod dms;
mod engrams;
mod enterprise_auth;
mod enterprise_relay_url;
mod export_util;
mod global_agent_config;
mod identity;

View File

@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
import { after, afterEach, before, test } from "node:test";
import { after, afterEach, before, mock, test } from "node:test";
import { JSDOM } from "jsdom";
const dom = new JSDOM("<!doctype html><html><body></body></html>", {
@@ -16,6 +16,7 @@ before(async () => {
Object.assign(globalThis, {
window: dom.window,
document: dom.window.document,
HTMLElement: dom.window.HTMLElement,
localStorage: dom.window.localStorage,
IS_REACT_ACT_ENVIRONMENT: true,
});
@@ -176,3 +177,116 @@ for (const failingUpdate of [0, 1]) {
assert.equal(pendingTrust.length, failingUpdate + 1);
});
}
function installTauriInvoke(handler) {
const previous = globalThis.window.__TAURI_INTERNALS__;
globalThis.window.__TAURI_INTERNALS__ = { invoke: handler };
globalThis.__TAURI_INTERNALS__ = globalThis.window.__TAURI_INTERNALS__;
return () => {
if (previous === undefined) {
delete globalThis.window.__TAURI_INTERNALS__;
delete globalThis.__TAURI_INTERNALS__;
} else {
globalThis.window.__TAURI_INTERNALS__ = previous;
globalThis.__TAURI_INTERNALS__ = previous;
}
};
}
function testCommunity() {
return {
id: "community-1",
name: "Enterprise",
relayUrl: "wss://enterprise.example",
token: "token-1",
reposDir: "/tmp/buzz-repos",
addedAt: "2026-09-17T00:00:00.000Z",
};
}
test("useCommunityInit gates enterprise login before applying the community", async () => {
const calls = [];
const restore = installTauriInvoke(async (command, args) => {
calls.push([command, args]);
if (command === "get_identity") {
return { pubkey: "pubkey-1", display_name: "Tester" };
}
if (command === "enterprise_login_gate") {
return { status: "notRequired" };
}
if (command === "apply_workspace") {
return null;
}
throw new Error(`unexpected command: ${command}`);
});
try {
const community = testCommunity();
const hook = renderHook(() =>
useCommunityInit(community, "community-key", false, true),
);
await waitFor(() => assert.equal(hook.result.current.isReady, true));
assert.deepEqual(
calls.map(([command]) => command),
["get_identity", "enterprise_login_gate", "apply_workspace"],
);
assert.deepEqual(calls[1], [
"enterprise_login_gate",
{ relayUrl: community.relayUrl },
]);
assert.deepEqual(calls[2], [
"apply_workspace",
{
relayUrl: community.relayUrl,
nsec: null,
token: community.token,
reposDir: community.reposDir,
agentManagedProfiles: false,
},
]);
hook.unmount();
} finally {
restore();
mock.reset();
}
});
test("useCommunityInit blocks community apply when enterprise login gate fails", async () => {
const consoleError = mock.method(console, "error", () => {});
const calls = [];
const restore = installTauriInvoke(async (command, args) => {
calls.push([command, args]);
if (command === "get_identity") {
return { pubkey: "pubkey-1", display_name: "Tester" };
}
if (command === "enterprise_login_gate") {
throw new Error("enterprise login unavailable");
}
if (command === "apply_workspace") {
throw new Error("apply_workspace must not run");
}
throw new Error(`unexpected command: ${command}`);
});
try {
const hook = renderHook(() =>
useCommunityInit(testCommunity(), "community-key", false, true),
);
await waitFor(() =>
assert.equal(hook.result.current.error, "enterprise login unavailable"),
);
assert.deepEqual(
calls.map(([command]) => command),
["get_identity", "enterprise_login_gate"],
);
assert.equal(consoleError.mock.calls.length, 1);
hook.unmount();
} finally {
restore();
mock.reset();
}
});