# How Buzz Manages Approval Tokens: Secure Workflow Gates Explained

> Learn how Buzz manages approval tokens securely. Discover its workflow gates using UUIDv4, SHA-256 hashing, and signed Nostr events for grants and denies.

- Repository: [Block Open Source/buzz](https://github.com/block/buzz)
- Tags: deep-dive
- Published: 2026-08-28

---

**Buzz implements cryptographically secure approval tokens using UUIDv4 generation and SHA-256 hashing, where raw tokens are never persisted—only hashed values are stored in the database, and approval actions are transmitted via signed Nostr events of kind 46030 (grant) or 46031 (deny).**

Buzz's workflow engine includes **approval-gate steps** that require human intervention before proceeding. When a run reaches a `request_approval` step, the system generates cryptographically random approval tokens that act as unforgeable credentials for authorizing or rejecting suspended workflows. Understanding how Buzz manages these approval tokens reveals a security-first architecture designed to prevent token leakage while enabling decentralized approval workflows.

## Token Generation and Workflow Suspension

### Minting Cryptographically Random Tokens

When the workflow executor encounters a `request_approval` step, it invokes `generate_approval_token` in [`crates/buzz-workflow/src/executor.rs`](https://github.com/block/buzz/blob/main/crates/buzz-workflow/src/executor.rs) at lines 773-781. This function generates a standard UUIDv4 using the operating system's CSPRNG via `Uuid::new_v4()`, ensuring 122 bits of entropy and unpredictability.

```rust
// crates/buzz-workflow/src/executor.rs
fn generate_approval_token(_run_id: Uuid, _step_id: &str) -> String {
    // Cryptographically secure random UUID
    Uuid::new_v4().to_string()
}

```

When the step executes, the executor returns `ExecutionResult::Suspended { approval_token: token }`, which triggers the workflow service to persist the hashed token and transition the run status.

### Run Status Management

The workflow run entering an approval gate receives `RunStatus::WaitingApproval`, defined in [`crates/buzz-db/src/workflow.rs`](https://github.com/block/buzz/blob/main/crates/buzz-db/src/workflow.rs) (lines 81-90). This status indicates the run is suspended pending external authorization. The hashed token links the suspended run to future grant or deny events, ensuring the correct workflow resumes when an approver acts.

## Secure Storage and Hashing

### SHA-256 Hash Implementation

Before any database persistence, the raw token undergoes hashing via `hash_approval_token` in [`crates/buzz-db/src/workflow.rs`](https://github.com/block/buzz/blob/main/crates/buzz-db/src/workflow.rs) (lines 29-35). The system stores only the 64-character hexadecimal SHA-256 digest, never the raw UUID.

```rust
// crates/buzz-db/src/workflow.rs
pub fn hash_approval_token(token: &str) -> String {
    let mut hasher = Sha256::new();
    hasher.update(token.as_bytes());
    format!("{:x}", hasher.finalize()) // 64-character hex string
}

```

### Community-Scoped Isolation

The `workflow_approvals` table stores the hashed token linked to a specific community, preventing cross-community misuse. As implemented in `block/buzz`, this design ensures that even with database access, an attacker cannot retrieve usable approval tokens or apply tokens across different community boundaries.

## Grant and Deny Operations via Nostr

### Event Structure and Kinds

Approvers submit signed Nostr events to authorize or reject workflows. The protocol uses distinct event kinds:

- **Kind 46030**: Approval grant events
- **Kind 46031**: Approval denial events

The desktop bridge and SDK use different tag conventions for the token payload. The desktop implementation in [`desktop/src-tauri/src/events/workflows.rs`](https://github.com/block/buzz/blob/main/desktop/src-tauri/src/events/workflows.rs) (lines 40-49) uses the `t` tag containing the **raw token**:

```rust
// desktop/src-tauri/src/events/workflows.rs
pub fn build_approval_grant(token: &str, note: Option<&str>) -> Result<EventBuilder, String> {
    let tags = vec![tag(vec!["t", token])?];           // “t” tag holds the raw token
    Ok(EventBuilder::new(Kind::Custom(46030), note.unwrap_or(""))
        .tags(tags))
}

```

Conversely, the SDK in [`crates/buzz-sdk/src/builders.rs`](https://github.com/block/buzz/blob/main/crates/buzz-sdk/src/builders.rs) (lines 49-71) uses the `d` tag with the **hashed token** for programmatic use:

```rust
// crates/buzz-sdk/src/builders.rs
pub fn build_workflow_approval(
    token_hash: &str,
    approved: bool,
    note: &str,
) -> Result<EventBuilder, SdkError> {
    // token_hash = hex‑encoded SHA‑256 of the raw token
    if token_hash.len() != 64 || !token_hash.chars().all(|c| c.is_ascii_hexdigit()) {
        return Err(SdkError::InvalidInput("token_hash must be a 64‑char hex SHA‑256 digest".into()));
    }
    let kind = if approved { KIND_APPROVAL_GRANT } else { KIND_APPROVAL_DENY };
    let tags = vec![tag(&["d", token_hash])?];        // SDK uses the “d” tag
    Ok(EventBuilder::new(Kind::Custom(kind as u16), note).tags(tags))
}

```

### CLI and UI Integration

The Tauri-based desktop application exposes three primary commands in [`desktop/src-tauri/src/commands/workflows.rs`](https://github.com/block/buzz/blob/main/desktop/src-tauri/src/commands/workflows.rs) (lines 33-70):

- `get_run_approvals`: Queries pending approvals for a specific run
- `grant_approval`: Submits grant events via the desktop bridge
- `deny_approval`: Submits denial events via the desktop bridge

Users invoke these through the Buzz CLI:

```bash
buzz workflow grant-approval <raw-token> --note "Looks good"

```

## Relay Processing and Workflow Resumption

### Token Validation Logic

When the relay receives an approval event, it validates the cryptographic signature and queries the database using `get_approval_by_stored_hash`. The system hashes the submitted raw token and compares it against the stored 64-character digest. Only matches against records with `Pending` status proceed to state transition.

### Resuming Suspended Runs

Upon successful validation, the approval record updates to `Granted` or `Denied`, and the workflow executor resumes the suspended run or marks it as failed. This logic is outlined in [`crates/buzz-workflow/src/lib.rs`](https://github.com/block/buzz/blob/main/crates/buzz-workflow/src/lib.rs), where the `approval_token.is_some()` guard processes the `ExecutionResult::Suspended` state and handles the "approval_not_supported" error placeholder for invalid transitions.

## Security Architecture

### Defense-in-Depth Guarantees

- **No plaintext storage**: Only SHA-256 hashes exist in the `workflow_approvals` table
- **Community isolation**: Tokens are scoped to specific communities, preventing cross-community replay attacks
- **Possession-based authorization**: Only holders of the raw UUID can construct valid Nostr events, as the relay requires the original token to match the stored hash
- **Cryptographic randomness**: UUIDv4 generation relies on the OS CSPRNG, making tokens unpredictable

## Summary

- Buzz generates approval tokens using `Uuid::new_v4()` in [`crates/buzz-workflow/src/executor.rs`](https://github.com/block/buzz/blob/main/crates/buzz-workflow/src/executor.rs), creating cryptographically random values that are unpredictable and unique.
- Raw tokens undergo immediate SHA-256 hashing via `hash_approval_token` in [`crates/buzz-db/src/workflow.rs`](https://github.com/block/buzz/blob/main/crates/buzz-db/src/workflow.rs), ensuring only 64-character hexadecimal digests persist in the database.
- Workflow runs entering approval gates receive `RunStatus::WaitingApproval` and suspend execution until an external Nostr event resolves the pending token.
- Approval actions transmit via signed Nostr events of kind 46030 (grant) or 46031 (deny), with the desktop bridge using `t` tags for raw tokens and the SDK using `d` tags for hashed values.
- The relay validates events by comparing SHA-256 hashes of submitted tokens against the `workflow_approvals` table, resuming runs only upon successful verification.

## Frequently Asked Questions

### What happens if someone gains access to the Buzz database?

Since [`crates/buzz-db/src/workflow.rs`](https://github.com/block/buzz/blob/main/crates/buzz-db/src/workflow.rs) stores only SHA-256 hashes of approval tokens (64-character hex strings), a database breach reveals no usable tokens. Attackers would need the original UUIDv4 values to construct valid Nostr grant or deny events, as the relay validates submitted tokens against stored hashes using the SHA-256 algorithm. Without the raw token, adversaries cannot generate matching hashes or sign valid approval events.

### Why does Buzz use different Nostr tags for desktop and SDK events?

The desktop bridge in [`desktop/src-tauri/src/events/workflows.rs`](https://github.com/block/buzz/blob/main/desktop/src-tauri/src/events/workflows.rs) uses the `t` tag to carry raw tokens for human-driven approvals, while [`crates/buzz-sdk/src/builders.rs`](https://github.com/block/buzz/blob/main/crates/buzz-sdk/src/builders.rs) uses the `d` tag with pre-hashed tokens for programmatic bot integrations. This distinction allows automated systems to work with hash values directly while keeping the human interface simple with raw UUID handling, accommodating different security models for interactive versus automated approval workflows.

### How does the workflow executor know when to resume a suspended run?

When a `request_approval` step executes, `generate_approval_token` in [`crates/buzz-workflow/src/executor.rs`](https://github.com/block/buzz/blob/main/crates/buzz-workflow/src/executor.rs) returns `ExecutionResult::Suspended` containing the approval token. The run status becomes `WaitingApproval` in the database. The relay later wakes the executor when it processes a matching Nostr event (kind 46030 or 46031) that validates against the stored hash, allowing the workflow to proceed or fail based on the approval decision contained in the event content.

### Can approval tokens be reused after being granted or denied?

No. Once the relay processes a grant or deny event and updates the approval record status to `Granted` or `Denied` in the `workflow_approvals` table, subsequent submissions with the same token hash fail validation. The system checks that the approval status remains `Pending` before accepting new events in [`crates/buzz-workflow/src/lib.rs`](https://github.com/block/buzz/blob/main/crates/buzz-workflow/src/lib.rs), ensuring single-use semantics and preventing replay attacks on completed approval workflows.