Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions desktop/scripts/check-file-sizes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,11 @@ const overrides = new Map([
// helpers) replace the pubkey-keyed PID file, plus the hashed pair-scoped
// runtime log path. Load-bearing crash-recovery surface; queued to split.
// internal-owner-only: persistence choke point normalizes local agent access.
["src-tauri/src/managed_agents/storage.rs", 1386],
// internal-local-relay-lockdown: load/save rejects legacy disallowed pins.
// internal-local-relay-lockdown round 3: fail-loud save baselines and regressions.
["src-tauri/src/managed_agents/storage.rs", 1496],
// internal-local-relay-lockdown rounds 2-3: guarded enrollment; OSS fast-path.
["src-tauri/src/huddle/mod.rs", 1007],
// harness-persona-sync: persona-runtime resolution threaded into the spawn
// path here. Load-bearing feature growth; queued to split in the resolver
// unify refactor followup. +26 for resolve_effective_prompt_model_provider
Expand All @@ -136,7 +140,8 @@ const overrides = new Map([
// lowest env layer (+8 lines). Queued to split.
// internal-owner-only: runtime authorization normalization protects stale
// or hand-edited records before spawning an internal managed agent.
["src-tauri/src/managed_agents/runtime.rs", 2228],
// internal-local-relay-lockdown: final spawn validates the effective relay.
["src-tauri/src/managed_agents/runtime.rs", 2229],
// config-bridge setup-payload env-boundary fix adds readiness wiring in
// spawn_agent_child; load-bearing security fix, queued to split.
["src-tauri/src/managed_agents/config_bridge/reader.rs", 1016],
Expand Down Expand Up @@ -463,7 +468,8 @@ const overrides = new Map([
// (if let Some(provider_update) = input.provider { record.provider = provider_update; }).
// +8: harness_override thread-through in update_managed_agent so a deliberate
// Custom pin routes to update_time_agent_command_override (comment + call).
["src-tauri/src/commands/agent_models.rs", 1079],
// +4: internal relay lockdown validates effective relay before update/profile sync.
["src-tauri/src/commands/agent_models.rs", 1083],
// global-agent-config: get_agent_config_surface / write_agent_config_field /
// put_agent_session_config commands + GlobalAgentConfig serde types. New file
// in this PR; queued to split with the command module refactor.
Expand Down
10 changes: 10 additions & 0 deletions desktop/src-tauri/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ fn main() {
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_OBSERVER_ARCHIVE_DEFAULT");
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_AGENT_METRIC_ARCHIVE_DEFAULT");
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_INTERNAL");
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_LOCAL_AGENT_RELAY_ALLOWLIST");
println!("cargo:rustc-check-cfg=cfg(buzz_updater_enabled)");

// Explicit distribution identity. Internal packaging sets this presence-only
Expand All @@ -24,6 +25,15 @@ fn main() {
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_INTERNAL=1");
}

// Newline-delimited exact relay URLs. Runtime parsing canonicalizes every
// entry with buzz-core before comparing an effective local-agent relay.
// Preserve malformed or empty input so internal builds fail loudly at the
// policy boundary instead of silently compiling to an empty allowlist.
if let Ok(raw) = std::env::var("BUZZ_BUILD_LOCAL_AGENT_RELAY_ALLOWLIST") {
let encoded = base64::engine::general_purpose::STANDARD.encode(raw.as_bytes());
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_LOCAL_AGENT_RELAY_ALLOWLIST={encoded}");
}

if let Ok(relay_url) = std::env::var("BUZZ_RELAY_URL") {
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_RELAY_URL={relay_url}");
}
Expand Down
32 changes: 31 additions & 1 deletion desktop/src-tauri/src/commands/agent_models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,22 @@ pub fn agent_access_owner_only() -> bool {
crate::managed_agents::internal_build()
}

/// Return whether local agents may attach to the active workspace relay.
#[tauri::command]
pub fn local_agent_relay_allowed(
state: tauri::State<'_, crate::app_state::AppState>,
) -> Result<bool, String> {
if !crate::managed_agents::internal_build() {
return Ok(true);
}
let relay_url = crate::relay::relay_ws_url_with_override(&state);
crate::managed_agents::validate_local_agent_relay(
&crate::managed_agents::BackendKind::Local,
&relay_url,
)?;
Ok(true)
}

/// Update mutable fields on an existing managed agent record.
///
/// Does NOT auto-restart the agent. Runtime config changes (system prompt,
Expand All @@ -785,6 +801,7 @@ pub async fn update_managed_agent(
state: State<'_, AppState>,
) -> Result<UpdateManagedAgentResponse, String> {
// Phase 1: local save (synchronous, under lock)
let workspace_relay_url = relay_ws_url_with_override(&state);
let (summary, sync_params, rollback) = {
let _store_guard = state
.managed_agents_store_lock
Expand Down Expand Up @@ -831,7 +848,13 @@ pub async fn update_managed_agent(
// value pins the agent; empty falls back to the workspace relay at
// read-time. A name-only edit (relay_url == None) leaves the pin intact.
if let Some(relay_url) = input.relay_url {
record.relay_url = relay_url.trim().to_string();
let relay_url = relay_url.trim().to_string();
crate::managed_agents::validate_effective_local_agent_relay(
&record.backend,
&relay_url,
&workspace_relay_url,
)?;
record.relay_url = relay_url;
}
if let Some(acp_command) = input.acp_command {
record.acp_command = acp_command;
Expand Down Expand Up @@ -888,6 +911,13 @@ pub async fn update_managed_agent(

record.updated_at = now_iso();

if name_changed {
crate::managed_agents::validate_effective_local_agent_relay(
&record.backend,
&record.relay_url,
&workspace_relay_url,
)?;
}
save_managed_agents(&app, &records)?;

let record = records
Expand Down
6 changes: 6 additions & 0 deletions desktop/src-tauri/src/commands/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ pub async fn create_managed_agent(
// Snapshot the workspace owner pubkey for the legacy-record auth_tag
// fallback. Computed outside the records lock to keep lock ordering simple.
let owner_hex = workspace_owner_hex(&state)?;
let workspace_relay_url = relay_ws_url_with_override(&state);

// ── Phase 1: generate keys (sync lock) ────────────────────────────────────
let (agent_keys, private_key_nsec, pubkey, resolved_relay_url, input) = {
Expand Down Expand Up @@ -632,6 +633,11 @@ pub async fn create_managed_agent(
.map(str::trim)
.unwrap_or("")
.to_string();
crate::managed_agents::validate_effective_local_agent_relay(
&input.backend,
&resolved_relay_url,
&workspace_relay_url,
)?;

(keys, private_key_nsec, pubkey, resolved_relay_url, input)
};
Expand Down
14 changes: 13 additions & 1 deletion desktop/src-tauri/src/commands/channels.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use tauri::State;
use tauri::{AppHandle, State};

use crate::{
app_state::AppState,
Expand Down Expand Up @@ -785,9 +785,14 @@ pub async fn add_channel_members(
channel_id: String,
pubkeys: Vec<String>,
role: Option<String>,
app: AppHandle,
state: State<'_, AppState>,
) -> Result<serde_json::Value, String> {
let uuid = parse_channel_uuid(&channel_id)?;
let relay_url = crate::relay::relay_ws_url_with_override(&state);
crate::managed_agents::validate_local_agent_members_from_store(&pubkeys, &relay_url, || {
crate::managed_agents::load_managed_agents(&app)
})?;
let role_str = match role.as_deref() {
Some("admin") => Some("admin"),
Some("bot") => Some("bot"),
Expand Down Expand Up @@ -833,6 +838,7 @@ pub async fn change_channel_member_role(
channel_id: String,
pubkey: String,
role: String,
app: AppHandle,
state: State<'_, AppState>,
) -> Result<(), String> {
let uuid = parse_channel_uuid(&channel_id)?;
Expand All @@ -843,6 +849,12 @@ pub async fn change_channel_member_role(
"owner" => return Err("cannot assign owner role — use transfer ownership".into()),
other => return Err(format!("invalid role: {other}")),
};
let relay_url = crate::relay::relay_ws_url_with_override(&state);
crate::managed_agents::validate_local_agent_members_from_store(
std::slice::from_ref(&pubkey),
&relay_url,
|| crate::managed_agents::load_managed_agents(&app),
)?;
let builder = events::build_add_member(uuid, &pubkey, Some(role_str))?;
submit_event(builder, &state).await?;
Ok(())
Expand Down
17 changes: 16 additions & 1 deletion desktop/src-tauri/src/huddle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub use transcription::{set_huddle_transcription_enabled, start_stt_pipeline};
// ── Imports ───────────────────────────────────────────────────────────────────

use std::sync::{atomic::Ordering, Arc};
use tauri::State;
use tauri::{AppHandle, State};
use uuid::Uuid;

use crate::{app_state::AppState, events, relay::submit_event};
Expand All @@ -94,6 +94,17 @@ fn normalize_huddle_channel_name(candidate: Option<String>, fallback: &str) -> S
name.chars().take(80).collect()
}

fn validate_huddle_agent_enrollment(
app: &AppHandle,
state: &AppState,
pubkeys: &[String],
) -> Result<(), String> {
let relay_url = crate::relay::relay_ws_url_with_override(state);
crate::managed_agents::validate_local_agent_members_from_store(pubkeys, &relay_url, || {
crate::managed_agents::load_managed_agents(app)
})
}

// ── Tauri commands ────────────────────────────────────────────────────────────

/// Set the voice input mode (push-to-talk or voice-activity detection).
Expand Down Expand Up @@ -162,6 +173,7 @@ pub async fn start_huddle(
parent_channel_id: String,
member_pubkeys: Vec<String>,
channel_name: Option<String>,
app: AppHandle,
state: State<'_, AppState>,
) -> Result<HuddleJoinInfo, String> {
// Validate inputs at the Tauri boundary.
Expand All @@ -184,6 +196,7 @@ pub async fn start_huddle(
}
deduped
};
validate_huddle_agent_enrollment(&app, &state, &member_pubkeys)?;

// Transition to Creating.
{
Expand Down Expand Up @@ -925,9 +938,11 @@ pub async fn speak_agent_message(text: String, state: State<'_, AppState>) -> Re
#[tauri::command]
pub async fn add_agent_to_huddle(
agent_pubkey: String,
app: AppHandle,
state: State<'_, AppState>,
) -> Result<agents::AgentAddResult, String> {
validate_pubkey_hex(&agent_pubkey)?;
validate_huddle_agent_enrollment(&app, &state, std::slice::from_ref(&agent_pubkey))?;

let (eph_id, parent_id) = {
let hs = state.huddle()?;
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,7 @@ pub fn run() {
get_agent_models,
discover_agent_models,
agent_access_owner_only,
local_agent_relay_allowed,
get_agent_config_surface,
get_runtime_file_config,
get_baked_build_env_keys,
Expand Down
5 changes: 5 additions & 0 deletions desktop/src-tauri/src/managed_agents/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub(crate) mod access_policy;
mod agent_env;
pub(crate) mod agent_events;
pub(crate) mod agent_snapshot;
pub(crate) mod relay_policy;
pub(crate) mod team_snapshot;
pub(crate) use access_policy::{
apply_update_access, internal_build, normalize_definition_access,
Expand All @@ -10,6 +11,10 @@ pub(crate) use access_policy::{
pub(crate) use agent_env::{
baked_build_env, build_buzz_agent_provider_defaults, discovery_env_with_baked_floor,
};
pub(crate) use relay_policy::{
validate_effective_local_agent_relay, validate_local_agent_members_from_store,
validate_local_agent_relay, validate_managed_agent_relay_pin,
};
mod backend;
pub(crate) mod config_bridge;
mod discovery;
Expand Down
Loading
Loading