mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(security): bump nostr crates for RUSTSEC-2026-0225..0232 + default sprig image to published digest (#4392)
## What Two changes, both fallout/follow-up from #4289 landing: ### 1. Fix the Security job failing on main (lockfile-only) Eight RUSTSEC advisories published today against the nostr stack turned `cargo-deny check` advisories red on main ([failing run](https://github.com/block/buzz/actions/runs/30761611723/job/91533106673)). Not introduced by #4289 — the advisories landed upstream and any push to main today would have tripped them. - **RUSTSEC-2026-0225..0230** → `nostr` 0.44.6 → **0.44.7** (Debug output exposing NIP-46/NIP-60 credentials; wallet parsers accepting unauthenticated events; NIP-44/NIP-04/NIP-98 resource exhaustion; NIP-50 empty-filter panic) - **RUSTSEC-2026-0231..0232** → `nostr-relay-pool` 0.44.2 (root) / 0.44.1 (tauri) → **0.44.3** (auth-challenge memory exhaustion; processing of unverified relay events) Both workspace lockfiles bumped (`Cargo.lock`, `desktop/src-tauri/Cargo.lock`). No manifest changes. ### 2. Default the desktop GUI's sprig image to the published `ghcr.io/block/buzz-sprig` The first main-push after #4289 published the image publicly (package created 18:44Z, visibility `public`). The `config_schema()`'s `image` property now carries a `default`: ``` ghcr.io/block/buzz-sprig:sha-6530b58@sha256:17facfc7608d8ddb33bc056c9aaba1098f4ef6abe5655702fbfd7584d1f74d76 ``` **Why tag+digest, not tag:** the backend deliberately rejects tag-only references — the pod runs with the agent's nsec and tags are mutable pointers (`image.rs` §Image). The tag+digest form keeps the human-traceable `sha-6530b58` while the digest does the pinning; `image::parse` already normalizes it to the tagless canonical form, so create-intent fingerprints are identical to the bare-digest spelling. The digest is the **multi-arch manifest-list digest** (amd64+arm64), resolved via `docker buildx imagetools inspect`. **This is a UI prefill, not a baked fallback:** `image` stays in the schema's `required` list, an empty value still fails closed with a named field, and the desktop submits the value explicitly in `provider_config` (the `WhereToRunSection` probe seeds `providerConfig` from schema defaults) — so deploy fingerprints never depend on compiled-in provider state, and the spec's §K8s pod-reconciliation concern about baked-default divergence is not engaged. Module prose that said "no published image exists yet" is updated to match reality. No desktop code changes needed: the form already prefills from `properties[*].default` and submits seeded defaults. ## Testing - `cargo-deny check` at head: **advisories ok, bans ok, licenses ok, sources ok** (was: advisories FAILED) - `cargo test -p buzz-backend-kubernetes`: **158 passed** (154 lib + 4 wire), including new `schema_default_image_round_trips_through_parse` pinning the constant + its normalization, and the wire `info` test now asserting the default is present in the provider's real stdout response - Live provider probe: `{"op":"info"}` against the built binary returns the default in `config_schema.properties.image.default` with `required` unchanged (`["namespace","image"]`) - Full workspace test suite via pre-push hook: green (earlier direct `cargo test --workspace` run: sole failure was `api::mesh_demo::demo_join_forwarded_arm_round_trips_echo`, the documented pre-existing main flake — unrelated, fails on base) - Image existence verified against GHCR: `docker buildx imagetools inspect ghcr.io/block/buzz-sprig:sha-6530b58` resolves to the pinned manifest-list digest with linux/amd64 + linux/arm64 manifests --------- Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
This commit is contained in:
co-authored by
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d
parent
f86cfc7369
commit
318fbf896e
Generated
+4
-4
@@ -5809,9 +5809,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nostr"
|
||||
version = "0.44.6"
|
||||
version = "0.44.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e826dd648489de2c5b293920e20b92932ef820302007c1987c758d4d06eeb2cf"
|
||||
checksum = "c7d3d987ea7078dc36947cde532637c472a229426702e4331dd7667325378bd9"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bech32",
|
||||
@@ -5853,9 +5853,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nostr-relay-pool"
|
||||
version = "0.44.2"
|
||||
version = "0.44.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb94d61a467a869a6790b907838a9bea82c813d567d9fcbac995f207be8cee4b"
|
||||
checksum = "c85c54d6ca9aae4ae2bf19a7663ba9db5f45f783f1d24aff55f006386b8b99a1"
|
||||
dependencies = [
|
||||
"async-utility",
|
||||
"async-wsocket",
|
||||
|
||||
@@ -526,11 +526,20 @@ staging *ARGS: bootstrap _ensure-sidecar-stubs
|
||||
FEATURES=(--features mesh-llm)
|
||||
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
||||
fi
|
||||
# Replace the 0-byte sidecar stub with the real CLI binary so tauri dev picks it up.
|
||||
# Replace 0-byte sidecar stubs with real binaries so tauri dev picks them up.
|
||||
# buzz: the CLI sidecar. buzz-backend-kubernetes: provider discovery scans the
|
||||
# exe dir for executable buzz-backend-* files, so the non-executable stub that
|
||||
# tauri dev copies next to the exe would hide the provider from "Run on".
|
||||
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
||||
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
|
||||
cp "${TARGET_DIR}/release/buzz" "desktop/src-tauri/binaries/buzz-${TARGET}"
|
||||
chmod +x "desktop/src-tauri/binaries/buzz-${TARGET}"
|
||||
STAGING_SIDECARS=(buzz)
|
||||
if [[ "$TARGET" != *windows* ]]; then
|
||||
STAGING_SIDECARS+=(buzz-backend-kubernetes)
|
||||
fi
|
||||
for bin in "${STAGING_SIDECARS[@]}"; do
|
||||
cp "${TARGET_DIR}/release/${bin}" "desktop/src-tauri/binaries/${bin}-${TARGET}"
|
||||
chmod +x "desktop/src-tauri/binaries/${bin}-${TARGET}"
|
||||
done
|
||||
cd {{desktop_dir}}
|
||||
export BUZZ_RELAY_URL="wss://sprout-oss.stage.blox.sqprod.co"
|
||||
source ../scripts/instance-env.sh
|
||||
@@ -553,11 +562,20 @@ production *ARGS: bootstrap _ensure-sidecar-stubs
|
||||
FEATURES=(--features mesh-llm)
|
||||
export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)"
|
||||
fi
|
||||
# Replace the 0-byte sidecar stub with the real CLI binary so tauri dev picks it up.
|
||||
# Replace 0-byte sidecar stubs with real binaries so tauri dev picks them up.
|
||||
# buzz: the CLI sidecar. buzz-backend-kubernetes: provider discovery scans the
|
||||
# exe dir for executable buzz-backend-* files, so the non-executable stub that
|
||||
# tauri dev copies next to the exe would hide the provider from "Run on".
|
||||
TARGET=$(rustc -vV | sed -n 's|host: ||p')
|
||||
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).target_directory")
|
||||
cp "${TARGET_DIR}/release/buzz" "desktop/src-tauri/binaries/buzz-${TARGET}"
|
||||
chmod +x "desktop/src-tauri/binaries/buzz-${TARGET}"
|
||||
PRODUCTION_SIDECARS=(buzz)
|
||||
if [[ "$TARGET" != *windows* ]]; then
|
||||
PRODUCTION_SIDECARS+=(buzz-backend-kubernetes)
|
||||
fi
|
||||
for bin in "${PRODUCTION_SIDECARS[@]}"; do
|
||||
cp "${TARGET_DIR}/release/${bin}" "desktop/src-tauri/binaries/${bin}-${TARGET}"
|
||||
chmod +x "desktop/src-tauri/binaries/${bin}-${TARGET}"
|
||||
done
|
||||
cd {{desktop_dir}}
|
||||
export BUZZ_RELAY_URL="wss://buzz.block.builderlab.xyz"
|
||||
source ../scripts/instance-env.sh
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//! `provider_config` parsing and the `info` config schema
|
||||
//! (spec §`provider_config` v1 fields, `docs/remote-agents.md:1384-1389`).
|
||||
//!
|
||||
//! Nine fields, all optional except `image` (v1 ships no baked default —
|
||||
//! §Image). No credential field exists, by I2: cluster auth comes from ambient
|
||||
//! Nine fields, all optional except `image` (required at parse time; the
|
||||
//! schema offers the published sprig image as a prefill default — §Image).
|
||||
//! No credential field exists, by I2: cluster auth comes from ambient
|
||||
//! kubeconfig resolution and nothing else (`:196-198`).
|
||||
|
||||
use crate::image::{self, ImageRef};
|
||||
@@ -33,6 +34,15 @@ impl Default for Resources {
|
||||
/// `BUZZ_ACP_EXIT_AFTER_INACTIVITY` are one knob, not two.
|
||||
pub const DEFAULT_INACTIVITY_SECONDS: u64 = 7200;
|
||||
|
||||
/// Default `image` schema prefill: the published sprig image, in tag+digest
|
||||
/// form so the tag stays human-traceable to its git SHA while the digest does
|
||||
/// the pinning (§Image — tag-only refs are rejected; `image::parse` drops the
|
||||
/// tag on normalization). This is a UI prefill, not a baked fallback: `image`
|
||||
/// stays required, an empty value still fails closed, and the value always
|
||||
/// arrives explicitly in `provider_config`, so create-intent fingerprints are
|
||||
/// unaffected by provider upgrades.
|
||||
pub const DEFAULT_IMAGE: &str = "ghcr.io/block/buzz-sprig:sha-6530b58@sha256:17facfc7608d8ddb33bc056c9aaba1098f4ef6abe5655702fbfd7584d1f74d76";
|
||||
|
||||
/// Fixed nonzero UID/GID for the agent container (§Pod shape hardening).
|
||||
pub const RUN_AS_UID: i64 = 10001;
|
||||
pub const RUN_AS_GID: i64 = 10001;
|
||||
@@ -201,7 +211,8 @@ pub fn config_schema() -> serde_json::Value {
|
||||
"image": {
|
||||
"type": "string",
|
||||
"title": "Agent image",
|
||||
"description": "Digest-pinned image containing the buzz-acp runtime ABI, e.g. ghcr.io/block/buzz-sprig@sha256:<digest>. Tags are not accepted: this pod holds the agent's private key."
|
||||
"description": "Digest-pinned image containing the buzz-acp runtime ABI, e.g. ghcr.io/block/buzz-sprig@sha256:<digest>. Tags alone are not accepted: this pod holds the agent's private key.",
|
||||
"default": DEFAULT_IMAGE
|
||||
},
|
||||
"cpu_request": {
|
||||
"type": "string", "title": "CPU request", "default": defaults.cpu_request
|
||||
@@ -396,6 +407,22 @@ mod tests {
|
||||
assert_eq!(parse(&cfg).unwrap().namespace, default);
|
||||
}
|
||||
|
||||
/// Same guarantee for the image prefill: the schema's default must be a
|
||||
/// value `image::parse` accepts, or the UI prefills a form that fails on
|
||||
/// submit. Its tag+digest form normalizes to the tagless canonical form.
|
||||
#[test]
|
||||
fn schema_default_image_round_trips_through_parse() {
|
||||
let schema = config_schema();
|
||||
let default = schema["properties"]["image"]["default"].as_str().unwrap();
|
||||
assert_eq!(default, DEFAULT_IMAGE);
|
||||
let cfg = serde_json::json!({"namespace": "buzz-agents-abc123", "image": default});
|
||||
let parsed = parse(&cfg).unwrap();
|
||||
assert_eq!(
|
||||
parsed.image.as_str(),
|
||||
"ghcr.io/block/buzz-sprig@sha256:17facfc7608d8ddb33bc056c9aaba1098f4ef6abe5655702fbfd7584d1f74d76"
|
||||
);
|
||||
}
|
||||
|
||||
/// Nine fields exactly (§`provider_config` v1 fields). The cap is 20; the
|
||||
/// count is pinned so a field added without a spec change is caught here.
|
||||
#[test]
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
//! distinguishes them from digests for exactly this reason — so a tag-only
|
||||
//! reference is rejected, not just `:latest`.
|
||||
//!
|
||||
//! v1 ships no baked default (there is no published `ghcr.io/block/buzz-sprig`
|
||||
//! image yet, so a compile-time digest would be a placeholder). `image` is
|
||||
//! therefore required, and its absence fails closed with a named field.
|
||||
//! There is no parse-time fallback: `image` is required, and its absence
|
||||
//! fails closed with a named field. The published `ghcr.io/block/buzz-sprig`
|
||||
//! digest is offered only as a schema `default` (a UI prefill the desktop
|
||||
//! submits explicitly — see `config::DEFAULT_IMAGE`), so the create-intent
|
||||
//! fingerprint never depends on compiled-in provider state.
|
||||
|
||||
/// A validated, digest-qualified image reference.
|
||||
///
|
||||
@@ -35,9 +37,9 @@ impl std::fmt::Display for ImageRef {
|
||||
pub fn parse(raw: &str) -> Result<ImageRef, String> {
|
||||
let reference = raw.trim();
|
||||
if reference.is_empty() {
|
||||
return Err("provider_config.image is required: v1 ships no default \
|
||||
image, so the digest-pinned image to run must be given \
|
||||
explicitly"
|
||||
return Err("provider_config.image is required: no image is assumed \
|
||||
at parse time, so the digest-pinned image to run must be \
|
||||
given explicitly"
|
||||
.to_string());
|
||||
}
|
||||
|
||||
@@ -169,7 +171,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// v1 has no baked default, so an absent image is an error that names the
|
||||
/// Parsing has no fallback (the schema default is a UI prefill, not a
|
||||
/// parse-time substitute), so an absent image is an error that names the
|
||||
/// field rather than a silent fallback.
|
||||
#[test]
|
||||
fn empty_reference_names_the_field() {
|
||||
|
||||
@@ -114,6 +114,14 @@ fn info_response_carries_the_contract_fields() {
|
||||
default.starts_with("buzz-agents-"),
|
||||
"unexpected namespace default: {default}"
|
||||
);
|
||||
let image_default = schema["properties"]["image"]["default"]
|
||||
.as_str()
|
||||
.expect("no image default");
|
||||
assert!(
|
||||
image_default.starts_with("ghcr.io/block/buzz-sprig:")
|
||||
&& image_default.contains("@sha256:"),
|
||||
"unexpected image default: {image_default}"
|
||||
);
|
||||
}
|
||||
|
||||
/// The desktop's richest payload must parse. No response fixture: this one
|
||||
|
||||
Generated
+4
-4
@@ -6124,9 +6124,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nostr"
|
||||
version = "0.44.6"
|
||||
version = "0.44.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e826dd648489de2c5b293920e20b92932ef820302007c1987c758d4d06eeb2cf"
|
||||
checksum = "c7d3d987ea7078dc36947cde532637c472a229426702e4331dd7667325378bd9"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bech32",
|
||||
@@ -6168,9 +6168,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nostr-relay-pool"
|
||||
version = "0.44.1"
|
||||
version = "0.44.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91b2c039df4f96c4bf7dae52a74fd5516ad6dda83a11c0c69dea91b5255a4f37"
|
||||
checksum = "c85c54d6ca9aae4ae2bf19a7663ba9db5f45f783f1d24aff55f006386b8b99a1"
|
||||
dependencies = [
|
||||
"async-utility",
|
||||
"async-wsocket",
|
||||
|
||||
Reference in New Issue
Block a user