How Buzz Manages Approval Tokens: Secure Workflow Gates Explained
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 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.
// 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 (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 (lines 29-35). The system stores only the 64-character hexadecimal SHA-256 digest, never the raw UUID.
// 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 (lines 40-49) uses the t tag containing the raw token:
// 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 (lines 49-71) uses the d tag with the hashed token for programmatic use:
// 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 (lines 33-70):
get_run_approvals: Queries pending approvals for a specific rungrant_approval: Submits grant events via the desktop bridgedeny_approval: Submits denial events via the desktop bridge
Users invoke these through the Buzz CLI:
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, 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_approvalstable - 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()incrates/buzz-workflow/src/executor.rs, creating cryptographically random values that are unpredictable and unique. - Raw tokens undergo immediate SHA-256 hashing via
hash_approval_tokenincrates/buzz-db/src/workflow.rs, ensuring only 64-character hexadecimal digests persist in the database. - Workflow runs entering approval gates receive
RunStatus::WaitingApprovaland 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
ttags for raw tokens and the SDK usingdtags for hashed values. - The relay validates events by comparing SHA-256 hashes of submitted tokens against the
workflow_approvalstable, 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 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 uses the t tag to carry raw tokens for human-driven approvals, while 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 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, ensuring single-use semantics and preventing replay attacks on completed approval workflows.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →