mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): preserve current workspace activation seams
Reconcile the multi-workspace agent stack with current main without dropping the authenticated-media redirect boundary, agent-managed profile setting, or shared-compute status publication. Keep the file-size exceptions explicit for the combined state and command wiring. Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
parent
b94c6e9ed3
commit
2b72652096
@@ -374,7 +374,11 @@ const overrides = new Map([
|
||||
// +5 (1068 -> 1073): merge with main, which independently added the
|
||||
// managed_agent_profile_reconcile_enabled flag (field + doc + init) under
|
||||
// its own 1042-line override. Union of two separately approved additions.
|
||||
["src-tauri/src/app_state.rs", 1054],
|
||||
["src-tauri/src/app_state.rs", 1090],
|
||||
// lazy-workspace-activation adds seven command/state wiring lines to lib.rs.
|
||||
// Keeping the registration and safety-gate setup together is clearer than a
|
||||
// one-off extraction whose only purpose would be satisfying the line cap.
|
||||
["src-tauri/src/lib.rs", 1007],
|
||||
// multi-slot splitting + no-op suppression (#1309): the ReadStateManager
|
||||
// class grew from ~700 lines to ~1019 with the addition of
|
||||
// splitContextsIntoBudgetedSlots (pure fn + 5 tests), publishSplitSlots,
|
||||
|
||||
@@ -18,6 +18,15 @@ use crate::managed_agents::ManagedAgentProcess;
|
||||
pub struct AppState {
|
||||
pub keys: Mutex<Keys>,
|
||||
pub http_client: reqwest::Client,
|
||||
/// A no-redirect client for authenticated relay media fetches (download,
|
||||
/// clipboard copy, snapshot, editor). Every caller pre-validates the URL
|
||||
/// origin, but the app-wide `http_client` follows redirects by default, so
|
||||
/// a relay `/media/` URL returning a 3xx to an off-origin or private host
|
||||
/// would forward the minted media Authorization header across origins —
|
||||
/// a redirect-hop SSRF. This client treats any 3xx as a non-success
|
||||
/// response (surfaced as an error) so the auth token never leaves the
|
||||
/// validated relay origin.
|
||||
pub media_fetch_client: reqwest::Client,
|
||||
/// Workspace-provided relay URL override. Set by `apply_workspace` on app
|
||||
/// init and takes priority over env vars and compile-time defaults.
|
||||
pub relay_url_override: Mutex<Option<String>>,
|
||||
@@ -155,6 +164,27 @@ fn identity_from_env() -> Option<Keys> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the no-redirect HTTP client used for authenticated relay media
|
||||
/// fetches (download / copy).
|
||||
///
|
||||
/// This client is a security boundary, not a convenience: it carries a minted
|
||||
/// media `Authorization` header, so it MUST NOT follow redirects. A relay 3xx
|
||||
/// to an off-origin or private host would otherwise forward that header across
|
||||
/// origins (a redirect-hop SSRF). `redirect::Policy::none()` returns the 3xx
|
||||
/// verbatim so the caller can reject it.
|
||||
///
|
||||
/// Returned as a `Result` so the fail-closed invariant is testable — callers
|
||||
/// must never substitute a redirect-following client on build failure. Shares
|
||||
/// the localhost `resolve`/pool config with the app-wide `http_client`.
|
||||
pub fn build_media_fetch_client() -> reqwest::Result<reqwest::Client> {
|
||||
reqwest::Client::builder()
|
||||
.resolve("localhost", std::net::SocketAddr::from(([127, 0, 0, 1], 0)))
|
||||
.pool_idle_timeout(std::time::Duration::from_secs(10))
|
||||
.pool_max_idle_per_host(1)
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn build_app_state() -> AppState {
|
||||
// Env var takes precedence (dev/CI). If absent, resolve_persisted_identity()
|
||||
// in setup() will replace the ephemeral placeholder with a persisted key.
|
||||
@@ -177,6 +207,11 @@ pub fn build_app_state() -> AppState {
|
||||
.pool_max_idle_per_host(1)
|
||||
.build()
|
||||
.unwrap_or_else(|_| reqwest::Client::new()),
|
||||
media_fetch_client: build_media_fetch_client().expect(
|
||||
"media_fetch_client must build with redirect::Policy::none(); a \
|
||||
redirect-following fallback would forward the minted media auth \
|
||||
header across origins (redirect-hop SSRF)",
|
||||
),
|
||||
relay_url_override: Mutex::new(None),
|
||||
managed_agent_restore_pending: AtomicBool::new(false),
|
||||
managed_agent_profile_reconcile_enabled: AtomicBool::new(true),
|
||||
|
||||
@@ -105,6 +105,7 @@ pub async fn apply_workspace(
|
||||
relay_url: String,
|
||||
nsec: Option<String>,
|
||||
repos_dir: Option<String>,
|
||||
agent_managed_profiles: Option<bool>,
|
||||
app: AppHandle,
|
||||
) -> Result<(), String> {
|
||||
let restore_app = app.clone();
|
||||
@@ -151,6 +152,13 @@ pub async fn apply_workspace(
|
||||
*keys_guard = keys;
|
||||
}
|
||||
|
||||
// Keep the backend-side reconcile guard aligned with the frontend
|
||||
// experiment before workspace activation can spawn any agents. Missing
|
||||
// means the stable behavior: desktop remains authoritative.
|
||||
state
|
||||
.managed_agent_profile_reconcile_enabled
|
||||
.store(!agent_managed_profiles.unwrap_or(false), Ordering::Release);
|
||||
|
||||
// ── One-shot legacy migration (non-fatal) ─────────────────────────────
|
||||
// Pin any blank-relay agent record to the first workspace applied
|
||||
// after boot — exactly what blank would have resolved to at boot
|
||||
|
||||
Reference in New Issue
Block a user