refactor(exec): centralize native approval delivery#57516
Conversation
Greptile SummaryThis PR refactors the exec-approval plumbing for Discord and Telegram by extracting a shared Key changes:
Minor findings (P2 only):
Confidence Score: 5/5Safe to merge — all remaining findings are P2 style/clarity suggestions that do not affect correctness. The PR introduces a well-structured shared runtime with solid test coverage across the new exec-approval-channel-runtime, the updated Discord/Telegram handlers, and the new commands.test.ts case for Telegram approvers. The two real bugs (approval buttons not resolving, typed /approve rejected for Telegram approvers) are correctly fixed and covered by tests. Only minor P2 issues remain. No files require special attention; the two P2 comments are in extensions/telegram/src/exec-approvals-handler.ts and extensions/telegram/src/exec-approval-resolver.ts.
|
| Filename | Overview |
|---|---|
| src/infra/exec-approval-channel-runtime.ts | New shared runtime correctly centralises gateway lifecycle, pending tracking, and timeout management with a clean generic adapter pattern. |
| extensions/telegram/src/exec-approval-resolver.ts | One-shot gateway resolver for Telegram button callbacks; minor undocumented contract around post-handshake close events. |
| extensions/telegram/src/exec-approvals-handler.ts | Migrated to shared runtime; finalizeResolved and clearPending are functionally identical with a void resolved; smell. |
| extensions/discord/src/monitor/exec-approvals.ts | Cleanly migrated to shared runtime; pending entries are now returned from deliverRequested rather than managed inline. |
| src/auto-reply/reply/commands-approve.ts | Correctly adds isAuthorizedTelegramApprovalSender bypass so configured Telegram approvers can use /approve regardless of general chat access. |
| extensions/telegram/src/bot-handlers.runtime.ts | Approval button callbacks now call resolveExecApproval and return early, fixing the pre-existing gap where button presses were silently dropped. |
| extensions/telegram/src/button-types.ts | Added correct 64-byte callback_data guard; filter applied per-chunk which is intentional and tested. |
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/telegram/src/exec-approvals-handler.ts
Line: 341
Comment:
**`void resolved` suppresses unused-param warning instead of fixing it**
`finalizeResolved` accepts a `resolved` parameter but never reads it — its body is identical to `clearPending`. The `void resolved;` statement is an uncommon idiom for suppressing the lint warning and is likely to confuse future readers.
The cleanest fix is to either prefix the parameter with `_` (standard TypeScript convention for intentionally unused params) or delegate directly to `clearPending`:
```ts
// Option A – rename and delegate
private async finalizeResolved(
_resolved: ExecApprovalResolved,
messages: PendingMessage[],
): Promise<void> {
await this.clearPending(messages);
}
```
Or, since both `finalizeResolved` and `finalizeExpired` do the exact same thing, the runtime constructor could just pass `clearPending` for both callbacks.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: extensions/telegram/src/exec-approval-resolver.ts
Line: 48-50
Comment:
**`onClose` fires as no-op once `onHelloOk` has already settled the promise**
After `onHelloOk` marks `readySettled = true`, a subsequent `onClose` call hits the early-return guard in `failReady` and does nothing. If the gateway drops the connection *between* `await ready` resolving and `await gatewayClient.request(...)` completing, there is no in-flight cancellation signal — the `request()` call itself must surface the error.
This is fine as long as `gatewayClient.request()` throws when the underlying socket is closed (which is the typical expectation), but it means a brief disconnect window after authentication produces a silent hang until the request eventually times out on the gateway side. Worth a comment here to make the contract explicit:
```suggestion
onClose: (code, reason) => {
// failReady is a no-op once onHelloOk has already resolved `ready`;
// any in-flight gatewayClient.request() is responsible for surfacing
// the disconnect as its own error at that point.
failReady(new Error(`gateway closed (${code}): ${reason}`));
},
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(exec): unify channel approval runtim..." | Re-trigger Greptile
b4ebef5 to
98d2f23
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 98d2f23177
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6d26857820
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d31154eb9c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
d31154e to
0fe7655
Compare
🔒 Aisle Security AnalysisWe found 3 potential security issue(s) in this PR:
1. 🟠 Discord native approval delivery trusts unverified origin channel from request fields/sessionKey
DescriptionThe Discord native approval adapter resolves the origin delivery target using request-controlled fields ( This enables a forged approval request (exec or plugin) to cause the system to send approval content and/or the “check your DMs” redirect notice to an attacker-chosen Discord channel ID:
Vulnerable code: if (turnSourceChannel === "discord" && turnSourceTo) {
if (params.accountId && turnSourceAccountId && normalizeAccountId(turnSourceAccountId) !== normalizeAccountId(params.accountId)) {
return null;
}
return { to: turnSourceTo };
}
const sessionTarget = resolveRequestSessionTarget(params);
if (!sessionTarget || sessionTarget.channel !== "discord") {
const channelId = extractDiscordChannelId(params.request.request.sessionKey?.trim() || null);
return channelId ? { to: channelId } : null;
}Downstream, RecommendationDo not trust Recommended fixes (choose one or combine):
const sessionTarget = resolveRequestSessionTarget(params);
if (!sessionTarget || sessionTarget.channel !== "discord") return null;
if (turnSourceChannel === "discord" && turnSourceTo) {
if (!params.accountId || !turnSourceAccountId) return null;
if (normalizeAccountId(turnSourceAccountId) !== normalizeAccountId(params.accountId)) return null;
// optionally: verify turnSourceTo equals sessionTarget.to (if sessionKey present)
}
2. 🟠 Authorization bypass for /approve when channel approval approvers list is empty (default-allow)
DescriptionThe new Several channel plugins implement
Impact:
Vulnerable code: const approvers = params.resolveApprovers({ cfg, accountId });
if (approvers.length === 0) {
return { authorized: true } as const;
}RecommendationChange the default behavior so an empty approver list does not grant approval permission. Safer options:
const approvers = params.resolveApprovers({ cfg, accountId });
if (approvers.length === 0) {
return {
authorized: false,
reason: `❌ No approvers are configured for ${approvalKind} approvals on ${params.channelLabel}.`,
} as const;
}Additionally, consider updating 3. 🟡 Telegram approval callback leaks raw error details into chat
DescriptionIn the Telegram callback handler, failures while resolving an approval callback are replied back into the originating chat with the raw stringified error.
Vulnerable code: } catch (resolveErr) {
const errStr = String(resolveErr);
logVerbose(`telegram: failed to resolve approval callback ${approvalCallback.approvalId}: ${errStr}`);
await replyToCallbackChat(`❌ Failed to submit approval: ${errStr}`);
return;
}RecommendationDo not echo raw exception strings to chats. Return a generic error to the user and log full details server-side. Example: } catch (resolveErr) {
logVerbose(
`telegram: failed to resolve approval callback ${approvalCallback.approvalId}: ${String(resolveErr)}`,
);
await replyToCallbackChat("❌ Failed to submit approval. Please try again or contact an administrator.");
return;
}Optionally, map known gateway errors to user-safe messages (e.g., "approval expired") and omit untrusted/verbose details. Analyzed PR: #57516 at commit Last updated on: 2026-03-30T15:44:26Z |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bd410cba47
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
297f8a3 to
7eb063e
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7eb063e70e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
7eb063e to
90df658
Compare
686e553 to
728f0ac
Compare
fca29c6 to
7f725bb
Compare
728f0ac to
933d9d8
Compare
7f725bb to
20111ae
Compare
20111ae to
9e1426b
Compare
0fae95b to
c625a3c
Compare
💡 Codex ReviewThis callback path resolves approvals whenever ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
|
Superseded by the reviewable split here:
Closing this stacked PR so the approval work only has two active review surfaces: one main approval/runtime/routing/auth PR and one shell |
Summary
Issues
Related: #57460
Related: #48532
Testing