block / block/buzz

Git push policy depends on a buzz-channel repo binding that no client can write

Open
#3,200 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

## Summary

The git push policy resolves a non-owner pusher's permissions through a `buzz-channel` tag on the repo's kind:30617 announcement. **No shipped client writes that tag**, so the binding is never present, so every non-owner push is rejected with `no channel binding` before role resolution is ever attempted.

The channel-role branch of the push policy is unreachable in practice. Repos are effectively owner-push-only.

## The read side

`crates/buzz-relay/src/api/git/policy.rs:300-305` resolves the channel from the announcement tags:

```rust
let channel_id = tags
.iter()
.find(|t| t.first().map(|s| s.as_str()) == Some("buzz-channel"))
.and_then(|t| t.get(1))
.and_then(|id| Uuid::parse_str(id).ok());
```

`policy.rs:347-356` then makes that tag load-bearing for authorization:

```rust
let role = if is_repo_owner || is_managed_agent_owner {
MemberRole::Owner
} else {
match channel_id {
None => {
warn!(repo = %req.repo_id, "hook callback: no buzz-channel binding");
return (StatusCode::FORBIDDEN, "no channel binding").into_response();
}
Some(ch_id) => {
match state.db.get_member_role(community, ch_id, &pusher_bytes).await {
```

`get_member_role`, and with it the entire Owner/Admin/Member/Bot ladder for git, is only reachable when `channel_id` is `Some`. It also gates the archived-channel check at `policy.rs:307-320`, which silently no-ops for every repo.

`web/src/features/repos/use-repos.ts:34` also reads the tag and surfaces it as `Repo.channelId` — always `null` today.

## The missing write side

I grepped the tree for `"buzz-channel"` at `afe4aaf530a36ee7837bb0efc8674b7503156cef`. Outside the two readers above, every remaining hit is a test fixture (`crates/buzz-cli/src/commands/repos.rs:428` and `crates/buzz-sdk/src/builders.rs:2842`, both under `#[cfg(test)]`). Neither client can emit it:

- **CLI** — `buzz repos create` accepts `--id`, `--name`, `--description`, `--clone`, `--web`, `--nostr-relay`. There is no channel flag.
- **Desktop** — `desktop/src/features/projects/useCreateProject.ts:53-67` builds the tag list as `d`, `name`, and conditionally `description`, `clone`, `web`. No channel tag, and no channel input in the create-project form.

Scope of that negative: rust/ts/tsx/mjs sources excluding `node_modules`. `build_repo_announcement_with_tags` (`crates/buzz-sdk/src/builders.rs:952`) takes arbitrary tags, so a direct SDK consumer or a raw Nostr client *can* set it — but nothing in Buzz itself does, and there is no command to add the tag to an existing announcement.

## Impact

1. Non-owner pushes are rejected regardless of channel role. Adding someone to a channel does not grant them git access, which appears to be the intended model given how `policy.rs` is written.
2. The archived-channel read-only guard never fires.
3. `Repo.channelId` is dead in the web client.
4. Separately from permissions: there is no way to express "this project belongs to this channel" anywhere in the product, so a project and its discussion channel have no navigable link in either direction.

## Suggested fix

Smallest version that makes the existing policy code reachable:

- `buzz repos create --channel ` emitting `["buzz-channel", ""]`.
- A channel selector in Desktop's create-project flow, writing the same tag.
- Something to set the binding on an already-announced repo, since kind:30617 is addressable and can be re-published.

Validating that the channel exists in the community at announce time would also give a better error than a push-time rejection.

Happy to open a PR for the CLI flag and the Desktop tag if the approach is agreeable — I have a working local build and test setup for this repo.

## Environment

Observed at `afe4aaf530a36ee7837bb0efc8674b7503156cef` (fork of `main`, two unrelated files changed). Static read of the policy path plus a full-tree grep; I have not reproduced a live rejected push against a running relay, so the runtime claim is inferred from the control flow above rather than observed.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.