Files
buzz/desktop/src-tauri/build.rs
16cc3de6d6 fix(desktop): enforce owner-only access in internal builds (#4053)
## Problem

Managed agents in internal Buzz builds should answer only their owner.
Previously, an agent could keep a broader access setting and respond to
other people, which did not match the access policy for internal builds.

This PR makes owner-only access effective for every managed agent in
internal builds and makes that restriction clear in the Desktop UI. Open
source builds remain configurable.

## Changes

- Enforce owner-only access when any managed agent starts or is deployed
from an internal build.
- Show the agent access control as locked to **Only me** in Desktop,
with an explanation of why it cannot be changed.
- Keep Welcome teammates working under the same rule without triggering
unnecessary restarts.
- Leave open source build behavior unchanged. This changes effective
runtime access without rewriting stored or relay-advertised settings.

The companion [#4064](https://github.com/block/buzz/pull/4064) explains
the restriction in-thread when someone without access mentions an agent.

The enforcement will remain inactive in shipped builds until
[squareup/buzz-releases#74](https://github.com/squareup/buzz-releases/pull/74)
marks internal releases during the build.

## Screenshots

| Before | After |
| --- | --- |
| ![Editable agent access control before the
change](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/4053/4053-before.png)
| ![Agent access locked to Only me in an internal
build](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/4053/4053-after-v2.png)
|

## Tests

Added coverage for:

- Runtime enforcement for [locally run
agents](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/runtime/tests.rs#L196)
and [deployed
agents](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/commands/agents_tests.rs#L510).
- The [current-build deployment
path](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/commands/agents_tests.rs#L455),
[invalid stored
access](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/access_policy.rs#L98),
and the [local startup
guard](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/env_vars/tests.rs#L149).
- Consistent enforcement across [both agent
backends](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src-tauri/src/managed_agents/access_policy.rs#L112).
- Welcome teammates created as [locally
run](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeGuide.test.mjs#L384)
or
[deployed](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeGuide.test.mjs#L393)
agents, including
[access-only](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeKickoff.test.mjs#L202)
and
[runtime-related](https://github.com/block/buzz/blob/e0165f52b52741a74184c9899e2b51eeec40c939/desktop/src/features/onboarding/welcomeKickoff.test.mjs#L225)
restart behavior.

The full Desktop Rust and JavaScript suites, type checks, formatting,
clippy, and file-size checks passed. Playwright E2E was not run.

---

Originated from Buzz channel
[buzz-agent-control](buzz://channel?id=cf5dada7-e26a-4887-ae41-b3bd5f42d3b2).
Supersedes #2537.

---------

Signed-off-by: Tom Brow <tomb@block.xyz>
Signed-off-by: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz>
Co-authored-by: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz>
Co-authored-by: Amp <amp@ampcode.com>
2026-08-05 19:58:24 -06:00

145 lines
6.8 KiB
Rust

// 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");
// 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");
use base64::Engine as _;
fn main() {
println!("cargo:rerun-if-env-changed=BUZZ_RELAY_URL");
println!("cargo:rerun-if-env-changed=BUZZ_RELAY_HTTP");
println!("cargo:rerun-if-env-changed=BUZZ_UPDATER_PUBLIC_KEY");
println!("cargo:rerun-if-env-changed=BUZZ_UPDATER_ENDPOINT");
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_BUZZ_AGENT_PROVIDER");
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_BUZZ_AGENT_MODEL");
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_AGENT_ENV");
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_RELAY_RECONNECT_CMD");
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY");
println!("cargo:rerun-if-env-changed=BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY");
println!("cargo:rustc-check-cfg=cfg(buzz_updater_enabled)");
// Explicit owner-only agent-access capability. Release packaging sets this
// presence-only marker; OSS/custom builds leave agent access configurable.
if std::env::var("BUZZ_BUILD_AGENT_ACCESS_OWNER_ONLY").is_ok() {
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_AGENT_ACCESS_OWNER_ONLY=1");
}
if let Ok(relay_url) = std::env::var("BUZZ_RELAY_URL") {
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_RELAY_URL={relay_url}");
}
if let Ok(relay_http) = std::env::var("BUZZ_RELAY_HTTP") {
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_RELAY_HTTP={relay_http}");
}
if let Ok(provider) = std::env::var("BUZZ_BUILD_BUZZ_AGENT_PROVIDER") {
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_BUZZ_AGENT_PROVIDER={provider}");
}
if let Ok(model) = std::env::var("BUZZ_BUILD_BUZZ_AGENT_MODEL") {
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_BUZZ_AGENT_MODEL={model}");
}
// Generic KEY=VALUE pairs to inject into every spawned agent process.
// Newline-delimited; each line must be non-empty and contain exactly one
// `=` separator with a non-empty key. OSS builds leave this unset.
// The validated value is base64-encoded before emitting so the single-line
// Cargo build-script output carries all pairs (Cargo output is line-oriented;
// a raw multiline value would be silently truncated to the first line).
if let Ok(raw) = std::env::var("BUZZ_BUILD_AGENT_ENV") {
for (line_no, line) in raw.lines().enumerate() {
let line = line.trim();
if line.is_empty() {
continue;
}
let eq = line.find('=').unwrap_or_else(|| {
panic!(
"BUZZ_BUILD_AGENT_ENV line {}: missing '=' separator in {:?}",
line_no + 1,
line
)
});
let key = &line[..eq];
if key.is_empty() {
panic!(
"BUZZ_BUILD_AGENT_ENV line {}: key must not be empty in {:?}",
line_no + 1,
line
);
}
// The baked env is written into every spawned agent's environment
// LAST (see `managed_agents/runtime.rs`), after Buzz sets the
// access gates and identity vars. A baked reserved key would
// therefore silently override the gate the UI promises, so reject
// it at build time instead of shipping a binary that bypasses its
// own enforcement.
if is_reserved_env_key(key) {
panic!(
"BUZZ_BUILD_AGENT_ENV line {}: `{}` is reserved by Buzz and cannot be baked \
into a build (it would override Buzz's own identity/access env)",
line_no + 1,
key
);
}
}
let encoded = base64::engine::general_purpose::STANDARD.encode(raw.as_bytes());
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_AGENT_ENV={encoded}");
}
if let Ok(val) = std::env::var("BUZZ_BUILD_RELAY_RECONNECT_CMD") {
let parsed: serde_json::Value = serde_json::from_str(&val)
.unwrap_or_else(|e| panic!("BUZZ_BUILD_RELAY_RECONNECT_CMD is not valid JSON: {e}"));
serde_json::from_value::<ReconnectHookConfig>(parsed).unwrap_or_else(|e| {
panic!("BUZZ_BUILD_RELAY_RECONNECT_CMD doesn't match ReconnectHookConfig: {e}")
});
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_RELAY_RECONNECT_CMD={val}");
}
// Presence-only release capability: internal desktop builds opt into
// auto-connecting their configured default relay on first run. OSS builds
// leave this unset and retain explicit community selection.
if std::env::var("BUZZ_BUILD_AUTO_CONNECT_DEFAULT_RELAY").is_ok() {
println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_AUTO_CONNECT_DEFAULT_RELAY=1");
}
let updater_public_key = std::env::var("BUZZ_UPDATER_PUBLIC_KEY")
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty());
let updater_endpoint = std::env::var("BUZZ_UPDATER_ENDPOINT")
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty());
if updater_public_key.is_some() && updater_endpoint.is_some() {
println!("cargo:rustc-cfg=buzz_updater_enabled");
}
// Cargo test executables get no embedded Windows manifest (tauri_build
// attaches one to bin targets only), so the loader binds comctl32 v5, which
// lacks TaskDialogIndirect (statically imported via tauri-plugin-dialog/rfd)
// and debug test exes die at load with STATUS_ENTRYPOINT_NOT_FOUND. Declaring
// the Common Controls v6 dependency makes link.exe emit a side-by-side
// <exe>.manifest that the loader honors for manifest-less executables;
// binaries with an embedded manifest (the real app) ignore it.
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows")
&& std::env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("msvc")
{
println!(
"cargo:rustc-link-arg=/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'"
);
}
tauri_build::try_build(
tauri_build::Attributes::new().plugin(
"websocket",
tauri_build::InlinedPlugin::new()
.commands(&["connect", "send", "disconnect", "disconnect_all"])
.default_permission(tauri_build::DefaultPermissionRule::AllowAllCommands),
),
)
.expect("failed to build Tauri application");
}