Skip to main content
PraisonAI bots can pin every interactive button (approve, deny, menu choice) to a specific set of users so other participants in the same chat cannot resolve it.
The user clicks Approve in a shared channel; only the requester and owners can resolve that callback.
Before PR #2307 (fixes #2299), any chat member could click another user’s approval button and resolve it. Tool approvals in shared chats are now actor-bound by default in the core wrapper, fail-closed.
Tool-approval Allow/Deny buttons ride a distinct interactive route (cmd:/approve <approval_id> <decision>) that bypasses the generic command policy — authorisation is enforced by the backend’s allowed_actors, not the per-command allow-list. Unknown decision values are rejected before hitting the backend. See Telegram Durable Approval.

Quick Start

1

Default — your bot is already protected

If you run a Telegram, Slack, or Discord bot using the built-in _presentation_approval wrapper, approvals are already pinned to the requester plus any configured owner/admins. No extra code required.
When the bot sends an approval prompt, only the user who triggered the tool call (plus owner_user_id and admin_users you configured) can click Approve or Deny. Other users in the same group chat are silently rejected.
2

Restrict approvals to a specific user

Pass allowed_actors when calling request_approval from a custom bot adapter to bind a single approval to one or more actors:
An unauthorized click leaves the approval pending — the legitimate actor can still resolve it.
3

Register a custom interactive button with authorization

Protect any interactive namespace (menus, polls, custom buttons) using an authorize callback:
If authorize returns False or raises, dispatch() returns False and the handler is never called. Fail-closed by design.

How It Works

Two layers of protection work together: the registry layer (any interactive callback) and the approval-wrapper layer (tool approvals specifically). State of an approval ID: A resolved approval ID is single-use — any second callback for it is a no-op, preventing replay attacks.

Which authorization shape do I need?


API Reference


Audit Log

Every resolution appends an entry to the bounded in-memory audit log. Retrieve it with the audit_log property (returns deep-copied dicts — callers cannot mutate internal state). Three categories of entries are written: Timeouts also append an entry (decision "timeout", approved=False, authorized=True if no actor restriction was set).

Common Patterns

Read the audit log after a session:
Cap memory for short-lived sessions:
The oldest entries are evicted once the limit is exceeded (bounded FIFO). Replay protection still holds for the most recent history_limit approvals. Check authorization without resolving:

Text-keyword fallback backends

TelegramApproval, SlackApproval, and DiscordApproval are separate from the _presentation_approval wrapper documented above. They poll for text keyword replies (or inline button taps on Telegram) and have their own actor allowlist added in PR #2582. Set allowed_approvers on the constructor (or via env var) to restrict who can resolve an approval from these backends:
With allowed_approvers=None (the default), legacy “any responder” behaviour is preserved for backward compatibility. You must set allowed_approvers explicitly to secure approvals in shared group chats.
The env var equivalents are TELEGRAM_APPROVERS, SLACK_APPROVERS, and DISCORD_APPROVERS (comma-separated user IDs). See the Approval Protocol page for full configuration tables.

Best Practices

The _presentation_approval wrapper does this automatically by passing allowed_actors including the requesting user, owner_user_id, and admin_users. Only override when you have a specific reason.
Authorizers run synchronously inside dispatch(). Any exception counts as a deny — the callback is rejected and an error is logged. Surface real errors in your application layer, not inside the authorizer.
The in-memory log is bounded by history_limit (default 1000). For compliance or long-running bots, push entries to your logging sink (e.g. structured logging, database) before they are evicted.
registry.register("namespace", handler) without authorize= allows any clicker — identical to the pre-PR behaviour. Useful for non-privileged callbacks such as read-only menus or informational polls.

Approval Protocol

Tool approval backends (console, Slack, Telegram, webhook)

Durable Approvals

Persist approvals to SQLite — including the --approval secure backend with actor authorisation and fail-closed start

Interactive Tool Approval

CLI/terminal approval flow and persistence

Interactive Bot Messages

Buttons, dropdowns, and approval prompts on Telegram/Slack/Discord

Bot Command Access Control

Restrict which users can run which bot commands

Text-keyword Backends

Text-keyword backends and their allowed_approvers allowlist