Close remaining local-agent relay gaps

Co-authored-by: npub102wg7q285p64ch2fjvstmf2ntn2sz3c4u5hmwatalc76mhsuauysftjtfj <7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz>
Signed-off-by: npub102wg7q285p64ch2fjvstmf2ntn2sz3c4u5hmwatalc76mhsuauysftjtfj <7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz>
This commit is contained in:
7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz
2026-07-23 13:17:12 -07:00
parent 62c9f36578
commit 93ea37c236
6 changed files with 105 additions and 9 deletions
+2 -1
View File
@@ -468,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.
+13 -3
View File
@@ -801,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
@@ -848,9 +849,11 @@ pub async fn update_managed_agent(
// read-time. A name-only edit (relay_url == None) leaves the pin intact.
if let Some(relay_url) = input.relay_url {
let relay_url = relay_url.trim().to_string();
if !relay_url.is_empty() {
crate::managed_agents::validate_local_agent_relay(&record.backend, &relay_url)?;
}
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 {
@@ -908,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
+6 -3
View File
@@ -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) = {
@@ -632,9 +633,11 @@ pub async fn create_managed_agent(
.map(str::trim)
.unwrap_or("")
.to_string();
if !resolved_relay_url.is_empty() {
crate::managed_agents::validate_local_agent_relay(&input.backend, &resolved_relay_url)?;
}
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)
};
@@ -838,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)?;
@@ -848,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(())
+2 -2
View File
@@ -12,8 +12,8 @@ pub(crate) use agent_env::{
baked_build_env, build_buzz_agent_provider_defaults, discovery_env_with_baked_floor,
};
pub(crate) use relay_policy::{
validate_local_agent_members_from_store, validate_local_agent_relay,
validate_managed_agent_relay_pin,
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;
@@ -80,6 +80,34 @@ where
))
}
pub(crate) fn validate_effective_local_agent_relay(
backend: &BackendKind,
relay_pin: &str,
workspace_relay_url: &str,
) -> Result<(), String> {
validate_effective_local_agent_relay_with_policy(
backend,
relay_pin,
workspace_relay_url,
super::internal_build(),
baked_allowlist,
)
}
fn validate_effective_local_agent_relay_with_policy<F>(
backend: &BackendKind,
relay_pin: &str,
workspace_relay_url: &str,
internal: bool,
allowlist: F,
) -> Result<(), String>
where
F: FnOnce() -> Result<Vec<String>, String>,
{
let effective = crate::relay::effective_agent_relay_url(relay_pin, workspace_relay_url);
validate_local_agent_relay_with_policy(backend, &effective, internal, allowlist)
}
/// Validate a legacy explicit record pin at persistence boundaries. Empty pins
/// are workspace-relative and are checked when their effective relay is known.
pub(crate) fn validate_managed_agent_relay_pin(record: &ManagedAgentRecord) -> Result<(), String> {
@@ -219,6 +247,44 @@ mod tests {
.is_ok());
}
#[test]
fn empty_pin_validates_the_effective_workspace_relay() {
assert!(validate_effective_local_agent_relay_with_policy(
&BackendKind::Local,
"",
"wss://public.example",
true,
allowlist,
)
.is_err());
assert!(validate_effective_local_agent_relay_with_policy(
&BackendKind::Local,
"",
"wss://buzz.block.builderlab.xyz",
true,
allowlist,
)
.is_ok());
}
#[test]
fn effective_relay_validation_skips_oss_and_provider_backends() {
let provider = BackendKind::Provider {
id: "provider".into(),
config: serde_json::json!({}),
};
for (backend, internal) in [(&BackendKind::Local, false), (&provider, true)] {
assert!(validate_effective_local_agent_relay_with_policy(
backend,
"",
"not-even-a-relay",
internal,
|| Err("allowlist must not load".into()),
)
.is_ok());
}
}
#[test]
fn member_enrollment_validates_only_matching_local_agents() {
let provider = BackendKind::Provider {
@@ -251,6 +317,15 @@ mod tests {
assert_eq!(calls.get(), 1);
}
#[test]
fn role_change_rejects_matching_local_agent_before_membership_emit() {
let records = vec![record("local", BackendKind::Local)];
let result = validate_local_agent_members_with(&records, &["LOCAL".into()], |backend| {
validate_local_agent_relay_with_policy(backend, "wss://public.example", true, allowlist)
});
assert!(result.is_err());
}
#[test]
fn oss_member_enrollment_does_not_load_the_agent_store() {
let loads = std::cell::Cell::new(0);