feat: add Slack approval buttons for tool execution in DMs - #796
Conversation
- Add RelayChannel and RelayClient for connecting to channel-relay SSE streams - Add RelayConfig with env-based configuration (CHANNEL_RELAY_URL, CHANNEL_RELAY_API_KEY) - Add channel-relay extension lifecycle: install, OAuth auth, activate with hot-add - Add proxy message sending through channel-relay for Slack chat.postMessage - Add extension registry entry for Slack relay with OAuth auth hint - Add relay integration test with mock SSE server - Wire relay channel into app startup with reconnect on stored credentials - Add AuthRequired extension error variant for cleaner auth flow detection [skip-regression-check]
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the bot's interaction with users on Slack by introducing a robust approval mechanism for tool execution. It enables interactive approval buttons directly within direct messages, while simultaneously preventing unauthorized tool execution in shared channels by automatically denying approval requests outside of DMs. This ensures secure and controlled tool usage, improving both user experience and system integrity. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces interactive approval buttons for tool execution in Slack DMs, a great enhancement for user experience and security. It also correctly auto-denies approval-requiring tools in shared channels to prevent prompt injection and stuck threads.
My review has identified a couple of issues:
- A high-severity bug in the auto-denial logic that could lead to confusing double error messages for the user.
- A critical security vulnerability related to
sender_idverification, which could potentially bypass the approval authorization check, violating the rule for verifying user IDs for resource access.
I've provided detailed comments and suggestions for both issues. Once these are addressed, this will be a solid feature.
Note: Security Review did not run due to the size of the PR.
zmanian
left a comment
There was a problem hiding this comment.
Review: feat: add Slack approval buttons for tool execution in DMs
Good feature -- auto-denying approval-requiring tools in shared relay channels prevents stuck threads and prompt injection from other users.
Design concerns:
-
Dead code in dispatcher.rs -- Lines 529-535 push to
preflightand assignpf_idxbut never use either. Thelet _ = pf_idx;suppresses the warning, but thepreflight.push()is completely unnecessary since you immediatelycontinuepast the tool. Remove both the push and thelet _ = pf_idx;. -
Channel detection is fragile --
message.channel.ends_with("-relay")is a string convention that could break if channel naming changes. Consider adding achannel_typefield toIncomingMessageor checking a more robust metadata flag. -
params_displayleaks tool parameters -- The Block Kit message includesserde_json::to_string_pretty(¶meters)which could contain sensitive data. Per project guidelines, tool parameters must be redacted before sending to external channels. Useredact_params()before formatting the display string. -
Missing
StatusUpdate::ApprovalNeededvariant -- The PR uses this enum variant insend_statusbut I don't see it added to theStatusUpdateenum in this diff. Is it added in a dependency PR?
Minor:
- Good defensive guard in
send_status(checkingevent_type != "direct_message"even though dispatcher already gates) value_strbeing the full JSON payload as the button value is fine for Slack's 2000-char limit but could hit that limit with large metadata -- consider truncating or using a request_id lookup instead
Would like the dead code and param redaction addressed before merge.
…uit breaker - Fix parser handle leak on reconnect by sharing Arc<RwLock> instead of creating a local copy in start() (shutdown now aborts the correct task) - Add CSRF state nonce to OAuth flow: generate in auth_channel_relay, validate in slack_relay_oauth_callback_handler, one-time use - Remove dead proxy_slack method, update integration test to use proxy_provider - Add reconnect circuit breaker (max_consecutive_failures, default 50) - Fix stale docs (Telegram refs), extract event_types constants
19990c6 to
c07346d
Compare
…tion - Remove second sleep+backoff in list_connections error branch to prevent O(4^n) backoff growth (was sleeping and doubling twice per iteration) - Buffer raw bytes in SSE parser instead of per-chunk String::from_utf8_lossy to prevent U+FFFD corruption when multi-byte chars span chunk boundaries
zmanian
left a comment
There was a problem hiding this comment.
Re-review: feat: add Slack approval buttons for tool execution in DMs
The single commit at 19:02 UTC does not appear to address any of the previous review feedback. The same issues remain:
1. Dead code / incorrect preflight handling in dispatcher.rs (still present)
Lines 529-535 push to preflight with PreflightOutcome::Runnable, assign pf_idx, then continue. This is wrong on two levels:
- The
let _ = pf_idx;is a dead-code suppression hack. - Marking as
Runnablethen continuing means the post-flight phase will see aRunnabletool with no corresponding result, producing a confusing "No result available" error in addition to the syntheticcontext_messages.push().
The correct fix (as also noted by Gemini) is to use PreflightOutcome::Rejected(...) and remove the manual context_messages.push(). The existing post-flight Rejected handler will emit the right tool result message:
preflight.push((
tc.clone(),
PreflightOutcome::Rejected(format!(
"Tool '{}' requires approval and cannot run in shared channels. \
Ask the user to message me directly (DM) to use this tool.",
tc.name
)),
));
continue;2. Missing sender_id validation (still present)
sender_id defaults to "" when absent from metadata. The value_payload then contains "sender_id": "", which weakens the downstream sender check on the Slack interactivity webhook -- any user could approve if the original sender_id was empty. This should error out like channel_id does:
let sender_id = metadata
.get("sender_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ChannelError::SendFailed {
name: self.name().to_string(),
reason: "Missing sender_id for approval buttons".into(),
})?;3. Channel detection is fragile (still present)
message.channel.ends_with("-relay") is a string convention. If relay channel naming changes, this silently breaks security (tools that should be auto-denied will instead hang waiting for approval in shared channels). Consider a more robust mechanism -- either a channel_type field on IncomingMessage, or checking metadata for a relay-specific flag.
4. Parameter redaction -- previous concern withdrawn
I previously flagged params_display as leaking tool parameters. After tracing the code, the parameters field reaching StatusUpdate::ApprovalNeeded comes from pending.display_parameters, which is already redacted via redact_params() in dispatcher.rs:789. So the Block Kit message shows redacted params. This is fine.
5. No tests
This PR has zero test coverage. At minimum:
- Unit test for the auto-deny logic in dispatcher (relay + non-DM -> tool rejected)
- Unit test for
send_statuswithApprovalNeededin DM context (blocks rendered correctly) - Unit test for
send_statuswithApprovalNeededin non-DM context (returns Ok without sending)
Per project conventions, fix commits must include regression tests.
6. Button value payload size
The value_str JSON payload in the button includes instance_id, team_id, channel_id, thread_ts, request_id, and sender_id. Slack limits button value to 2000 characters. While this is unlikely to hit the limit with typical UUIDs, it's worth adding a length check or a comment documenting the constraint.
Items 1, 2, and 5 are blocking. Items 3 and 6 are strong recommendations.
Send Block Kit Approve/Deny buttons via relay when a tool requires approval in a DM context. Auto-deny approval-requiring tools in shared channels to prevent prompt injection and stuck threads.
c07346d to
a0deb2b
Compare
fdb67ff to
aa25f85
Compare
zmanian
left a comment
There was a problem hiding this comment.
Re-review after new commit. 1 of 3 items addressed:
- Dead code in dispatcher.rs -- Fixed. Clean auto-deny flow now.
- Channel detection fragility -- Still uses
ends_with("-relay"). Non-blocking, can be a follow-up. - [Security] params_display leaks tool parameters to Slack -- Still uses
serde_json::to_string_pretty(¶meters)withoutredact_params(). Per project rules, tool parameters must be redacted before sending to external channels. Sensitive data (API keys, file contents) in tool params would be sent verbatim to Slack. This is blocking.
Fix for #3: Replace the raw serialization with redact_params(¶meters, tool.sensitive_params()) before building params_display.
(Dropping item #4 from original review -- StatusUpdate::ApprovalNeeded already existed in codebase.)
…l-buttons # Conflicts: # src/channels/relay/channel.rs # src/extensions/manager.rs # src/extensions/registry.rs # src/main.rs
zmanian
left a comment
There was a problem hiding this comment.
Re-review: feat: add Slack approval buttons for tool execution in DMs
The PR scope has narrowed significantly after the staging merge -- the diff now only touches src/agent/dispatcher.rs (auto-deny logic) and a stray test_clean.db.
Previous feedback status
-
Dead code / preflight handling -- FIXED. The old
preflight.push(Runnable)+let _ = pf_idx+continuepattern is gone. The new approach pushes a synthetictool_resultdirectly toreason_ctx.messagesandcontinues past the preflight/runnable push. This is clean and correct -- the auto-denied tool never enters preflight or runnable, so Phase 3 won't produce a spurious "No result available" error. -
Channel detection fragility -- Still uses
ends_with("-relay"). Acknowledged as non-blocking, fine for a follow-up. -
sender_idvalidation / approval buttons insend_status-- No longer in this PR's diff. The relay channel'ssend_statusis still a no-op on this branch. The PR title ("add Slack approval buttons") is misleading -- this PR only adds the auto-deny half. The actual button rendering presumably comes in a follow-up. Consider updating the PR title to match the actual scope (e.g., "auto-deny approval-requiring tools in non-DM relay channels"). -
Parameter redaction -- Not applicable since approval buttons are not in this diff.
New issues
[Blocking] test_clean.db committed to the repo. A 590KB SQLite database file (test_clean.db) is included in this PR. This should not be committed -- add it to .gitignore or remove it from the branch.
[Non-blocking] No tests. The auto-deny logic in dispatcher.rs has zero test coverage. A unit test exercising the is_relay && !is_dm path (tool gets a synthetic rejection, loop continues) would be valuable. Per project conventions, fix/feature commits should include regression tests. Given the small scope of this change, this is a strong recommendation but not blocking.
Summary
The dispatcher auto-deny logic is correct and clean. The one blocking issue is the stray test_clean.db binary. Remove it and this is ready to merge (assuming the PR title/description are updated to reflect the actual scope).
zmanian
left a comment
There was a problem hiding this comment.
Re-review: feat: add Slack approval buttons for tool execution in DMs
The PR scope has narrowed significantly after the staging merge -- the diff now only touches src/agent/dispatcher.rs (auto-deny logic) and a stray test_clean.db.
Previous feedback status
-
Dead code / preflight handling -- FIXED. The old
preflight.push(Runnable)+let _ = pf_idx+continuepattern is gone. The new approach pushes a synthetictool_resultdirectly toreason_ctx.messagesandcontinues past the preflight/runnable push. This is clean and correct -- the auto-denied tool never enters preflight or runnable, so Phase 3 won't produce a spurious "No result available" error. -
Channel detection fragility -- Still uses
ends_with("-relay"). Acknowledged as non-blocking, fine for a follow-up. -
sender_id validation / approval buttons in send_status -- No longer in this PR's diff. The relay channel's
send_statusis still a no-op on this branch. The PR title ("add Slack approval buttons") is misleading -- this PR only adds the auto-deny half. The actual button rendering presumably comes in a follow-up. Consider updating the PR title to match the actual scope (e.g., "auto-deny approval-requiring tools in non-DM relay channels"). -
Parameter redaction -- Not applicable since approval buttons are not in this diff.
New issues
[Blocking] test_clean.db committed to the repo. A 590KB SQLite database file (test_clean.db) is included in this PR. This should not be committed -- add it to .gitignore or remove it from the branch.
[Non-blocking] No tests. The auto-deny logic in dispatcher.rs has zero test coverage. A unit test exercising the is_relay && !is_dm path (tool gets a synthetic rejection, loop continues) would be valuable. Per project conventions, fix/feature commits should include regression tests. Given the small scope of this change, this is a strong recommendation but not blocking.
Summary
The dispatcher auto-deny logic is correct and clean. The one blocking issue is the stray test_clean.db binary. Remove it and this is ready to merge (assuming the PR title/description are updated to reflect the actual scope).
- Auto-deny in non-DM relay channels now uses PreflightOutcome::Rejected instead of manually pushing to reason_ctx.messages, so the post-flight handler properly records the error in the turn - Add regression tests for relay auto-deny decision logic - Remove test_clean.db artifact
Status: Review feedback addressedPushed Fixed
Previously addressed (in earlier commits)
Non-blocking (noted, not fixed)
CI
|
The send_status implementation was accidentally dropped during the staging merge. Restores Approve/Deny Block Kit buttons for DM tool approval, with required sender_id validation, payload size docs, and 4 regression tests. Also removes test_clean.db. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Status: All review feedback addressedPushed Review item tracker
What changed in
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
zmanian
left a comment
There was a problem hiding this comment.
All previous blocking items are addressed. The current diff is clean and correct.
Security: Parameters reaching send_status are already redacted (display_parameters from redact_params in dispatcher.rs:859). sender_id and channel_id are both required with proper error returns. Auto-deny in non-DM relay channels prevents prompt injection and stuck AwaitingApproval state. DM-only guard in send_status is good defense-in-depth.
Code correctness: No .unwrap() in production code. PreflightOutcome::Rejected pattern is now used correctly in dispatcher auto-deny. Error handling is consistent with project conventions.
Tests: Good coverage -- non-approval noop, non-DM skip, missing channel_id, missing sender_id, auto-deny decision logic.
Non-blocking notes for follow-up:
ends_with("-relay")channel detection is fragile (acknowledged in prior reviews, fine as follow-up)- Button
value_strlength is unchecked against Slack's 2000-char limit -- unlikely to hit with UUIDs but worth a bounds check or comment - The approve_tool/deny_tool action_id handlers are presumably on the relay service side -- confirm the callback flow works end-to-end in integration testing
* feat: add channel-relay integration for Slack via external relay service - Add RelayChannel and RelayClient for connecting to channel-relay SSE streams - Add RelayConfig with env-based configuration (CHANNEL_RELAY_URL, CHANNEL_RELAY_API_KEY) - Add channel-relay extension lifecycle: install, OAuth auth, activate with hot-add - Add proxy message sending through channel-relay for Slack chat.postMessage - Add extension registry entry for Slack relay with OAuth auth hint - Add relay integration test with mock SSE server - Wire relay channel into app startup with reconnect on stored credentials - Add AuthRequired extension error variant for cleaner auth flow detection [skip-regression-check] * chore: apply cargo fmt * fix: remove remaining Telegram test references in relay channel * fix: address PR nearai#790 review feedback — parser handle leak, CSRF, circuit breaker - Fix parser handle leak on reconnect by sharing Arc<RwLock> instead of creating a local copy in start() (shutdown now aborts the correct task) - Add CSRF state nonce to OAuth flow: generate in auth_channel_relay, validate in slack_relay_oauth_callback_handler, one-time use - Remove dead proxy_slack method, update integration test to use proxy_provider - Add reconnect circuit breaker (max_consecutive_failures, default 50) - Fix stale docs (Telegram refs), extract event_types constants * fix: double backoff in reconnect loop and UTF-8 chunk-boundary corruption - Remove second sleep+backoff in list_connections error branch to prevent O(4^n) backoff growth (was sleeping and doubling twice per iteration) - Buffer raw bytes in SSE parser instead of per-chunk String::from_utf8_lossy to prevent U+FFFD corruption when multi-byte chars span chunk boundaries * feat: add Slack approval buttons for tool execution in DMs Send Block Kit Approve/Deny buttons via relay when a tool requires approval in a DM context. Auto-deny approval-requiring tools in shared channels to prevent prompt injection and stuck threads. * fix: address PR nearai#796 review — use PreflightOutcome::Rejected, add tests - Auto-deny in non-DM relay channels now uses PreflightOutcome::Rejected instead of manually pushing to reason_ctx.messages, so the post-flight handler properly records the error in the turn - Add regression tests for relay auto-deny decision logic - Remove test_clean.db artifact * feat: restore Block Kit approval buttons in send_status The send_status implementation was accidentally dropped during the staging merge. Restores Approve/Deny Block Kit buttons for DM tool approval, with required sender_id validation, payload size docs, and 4 regression tests. Also removes test_clean.db. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: apply rustfmt formatting to dispatcher test code Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add channel-relay integration for Slack via external relay service - Add RelayChannel and RelayClient for connecting to channel-relay SSE streams - Add RelayConfig with env-based configuration (CHANNEL_RELAY_URL, CHANNEL_RELAY_API_KEY) - Add channel-relay extension lifecycle: install, OAuth auth, activate with hot-add - Add proxy message sending through channel-relay for Slack chat.postMessage - Add extension registry entry for Slack relay with OAuth auth hint - Add relay integration test with mock SSE server - Wire relay channel into app startup with reconnect on stored credentials - Add AuthRequired extension error variant for cleaner auth flow detection [skip-regression-check] * chore: apply cargo fmt * fix: remove remaining Telegram test references in relay channel * fix: address PR nearai#790 review feedback — parser handle leak, CSRF, circuit breaker - Fix parser handle leak on reconnect by sharing Arc<RwLock> instead of creating a local copy in start() (shutdown now aborts the correct task) - Add CSRF state nonce to OAuth flow: generate in auth_channel_relay, validate in slack_relay_oauth_callback_handler, one-time use - Remove dead proxy_slack method, update integration test to use proxy_provider - Add reconnect circuit breaker (max_consecutive_failures, default 50) - Fix stale docs (Telegram refs), extract event_types constants * fix: double backoff in reconnect loop and UTF-8 chunk-boundary corruption - Remove second sleep+backoff in list_connections error branch to prevent O(4^n) backoff growth (was sleeping and doubling twice per iteration) - Buffer raw bytes in SSE parser instead of per-chunk String::from_utf8_lossy to prevent U+FFFD corruption when multi-byte chars span chunk boundaries * feat: add Slack approval buttons for tool execution in DMs Send Block Kit Approve/Deny buttons via relay when a tool requires approval in a DM context. Auto-deny approval-requiring tools in shared channels to prevent prompt injection and stuck threads. * fix: address PR nearai#796 review — use PreflightOutcome::Rejected, add tests - Auto-deny in non-DM relay channels now uses PreflightOutcome::Rejected instead of manually pushing to reason_ctx.messages, so the post-flight handler properly records the error in the turn - Add regression tests for relay auto-deny decision logic - Remove test_clean.db artifact * feat: restore Block Kit approval buttons in send_status The send_status implementation was accidentally dropped during the staging merge. Restores Approve/Deny Block Kit buttons for DM tool approval, with required sender_id validation, payload size docs, and 4 regression tests. Also removes test_clean.db. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: apply rustfmt formatting to dispatcher test code Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
AwaitingApprovalthreadsevent_typeto relay metadata sosend_statuscan distinguish DMs from channel messagesFollow-up to #790.
Test plan
cargo clippyandcargo testpass