mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Fixes all six HIGH findings from the buzz security report, one commit per finding. Independently reviewed to approval by Max at `0158ae542`, plus a deep isolated live pass (clean-room compose stack, weird ports, full product matrix) at the same head — see the buzz-security thread for evidence. `fe65c07c3` merges current `origin/main` on top (new commit, no rebase), inheriting the nostr 0.44.6 bump (#3135) and relay-admin ban gate (#3128). ## Findings and fixes | Finding | Commit | Fix | |---|---|---| | 003 — quinn-proto RUSTSEC-2026-0185 | `e5dcdec72` | Bump quinn-proto 0.11.14 → 0.11.16 (lockfile-only) | | 002/004 — linkify-it quadratic-parse DoS (GHSA-22p9-wv53-3rq4, GHSA-v245-v573-v5vm) | `923b3c20f` | pnpm override `linkify-it: ^5.0.2`; `pnpm why` confirms a single 5.0.2 copy | | 001 — media reads served unauthenticated by default | `0f277e3e2` | Helm `requireMediaGetAuth` defaults to `true` + rendered-chart test pinning the default | | 006 — removed workflow owners retain webhook-exfiltration authority | `4749bd56c` | Fail-closed per-fire authority gate (current owner/admin membership) on **all four** trigger doors (on_event, scheduler pre-claim, manual trigger, webhook — masked as generic 404), save-time gate for `call_webhook` defs, durable disable-on-removal wired to kinds 9001 + 9022 | | 005 — git Smart-HTTP reads ignore channel membership | `e648f2dba` + `0158ae542` | `authorize_git_read`: caller's **current active membership** in the repo's bound channel, checked before any hydration/subprocess on all three read doors (`info_refs` for both services + `upload_pack` POST). Uniform generic 404 denials (no membership probing), no repo-owner bypass, first-`buzz-channel`-tag binding semantics fail closed on ambiguous duplicates (mutation-verified test). Resolution follows the live kind:30617 announcement, so deleted/replaced announcements deny immediately. The committed `e2e-git-perms.sh` guest scenario previously asserted the vulnerability — now asserts denial. | ## Behavior changes to be aware of 1. **Unbound repos fail closed for git reads.** `buzz repos create` emits no `buzz-channel` tag, so CLI-created repos without a binding are unreadable via git HTTP. Correct per finding 005's fail-closed posture; a follow-up could bind CLI-created repos at creation time. 2. **006 is conservative:** a workflow disabled on owner removal does not auto-re-enable if the owner is re-added — explicit re-enable required. 3. Merge conflict resolution in `fe65c07c3`: kept main's `@radix-ui/react-dismissable-layer` 1.1.19 bump alongside the linkify-it security override (`pnpm-workspace.yaml` + lockfile). ## Verification at the merge head `fe65c07c3` (same shell) - buzz-relay `--lib`: 761 passed / 1 failed — the lone red is the known pre-existing `mesh_demo::demo_join_forwarded_arm_round_trips_echo` 504 flake, present on main - SEC-005 module incl. PG behavioral matrix: 8/8 (removed-member, never-member, owner-no-bypass, deleted-30617, malformed/ambiguous binding, owner-mismatch all denied) - buzz-workflow 153/0, buzz-db 84/0; `clippy --all-targets -D warnings` + `fmt --check` clean - Desktop JS 3637/3637, tsc clean, biome clean, file-size/px-text/pubkey-truncation gates clean - `helm lint` + `helm unittest` (40/40) on `deploy/charts/buzz` - All five pre-push hooks green (desktop-check, desktop-test, rust-tests, desktop-tauri-test, branch-skew) Prior review evidence at `0158ae542` (pre-merge): Max's independent exact-head approval + clean-room live regression pass (`WORK_LOGS/2026-07-27_SECURITY_HIGH_LIVE_TEST.md` in his workspace). Max will re-run the deep local pass at this post-merge head before merge. --------- Signed-off-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
73 lines
2.2 KiB
Rust
73 lines
2.2 KiB
Rust
//! Workflow error types.
|
|
|
|
use thiserror::Error;
|
|
|
|
/// Partial execution progress captured when a workflow step fails mid-run.
|
|
///
|
|
/// This allows callers to persist whatever trace was accumulated before the
|
|
/// error, rather than losing it when the in-memory `Vec` is dropped.
|
|
#[derive(Debug, Default)]
|
|
pub struct PartialProgress {
|
|
/// Index of the step that failed (0-based).
|
|
pub step_index: usize,
|
|
/// Trace entries for steps completed/skipped before the failure.
|
|
pub trace: Vec<serde_json::Value>,
|
|
}
|
|
|
|
/// Errors produced by the workflow engine.
|
|
#[derive(Debug, Error)]
|
|
pub enum WorkflowError {
|
|
/// The workflow YAML/JSON could not be parsed.
|
|
#[error("invalid YAML: {0}")]
|
|
InvalidYaml(#[from] serde_yaml::Error),
|
|
|
|
/// The workflow definition violates a semantic invariant.
|
|
#[error("invalid definition: {0}")]
|
|
InvalidDefinition(String),
|
|
|
|
/// An `if:` condition expression could not be evaluated.
|
|
#[error("condition evaluation error: {0}")]
|
|
ConditionError(String),
|
|
|
|
/// A template variable substitution failed.
|
|
#[error("template error: {0}")]
|
|
TemplateError(String),
|
|
|
|
/// A step exceeded its configured timeout.
|
|
#[error("step '{step_id}' timed out after {timeout_secs}s")]
|
|
StepTimeout {
|
|
/// The ID of the step that timed out.
|
|
step_id: String,
|
|
/// The timeout limit in seconds.
|
|
timeout_secs: u64,
|
|
},
|
|
|
|
/// An outbound webhook call failed.
|
|
#[error("webhook error: {0}")]
|
|
WebhookError(String),
|
|
|
|
/// The engine's concurrency limit was reached.
|
|
#[error("capacity exceeded")]
|
|
CapacityExceeded,
|
|
|
|
/// A database operation failed.
|
|
#[error("database error: {0}")]
|
|
Database(String),
|
|
|
|
/// The workflow's owner is not currently authorized to run it (removed
|
|
/// from the channel, insufficient role for the definition's actions, or
|
|
/// the authority lookup failed — all deny, fail-closed).
|
|
#[error("unauthorized: {0}")]
|
|
Unauthorized(String),
|
|
|
|
/// The action is defined but not yet implemented.
|
|
#[error("action not implemented: {0}")]
|
|
NotImplemented(String),
|
|
}
|
|
|
|
impl From<buzz_db::error::DbError> for WorkflowError {
|
|
fn from(e: buzz_db::error::DbError) -> Self {
|
|
WorkflowError::Database(e.to_string())
|
|
}
|
|
}
|