Skip to content

Fix issues #42 and #44, and improve settings panel usability#47

Merged
friuns2 merged 3 commits intofriuns2:mainfrom
kinogram:main
Apr 12, 2026
Merged

Fix issues #42 and #44, and improve settings panel usability#47
friuns2 merged 3 commits intofriuns2:mainfrom
kinogram:main

Conversation

@kinogram
Copy link
Copy Markdown
Contributor

修复 issue #42#44,并顺手修了设置列表无法滚动的显示问题。

修复如下:

  1. 新线程首条消息在服务端确认持久化前禁止 Stop
  • 等待期间 Stop 按钮显示转圈动效
  • 服务端确认后再解锁 Stop
  • 不影响已有线程的正常中断流程
  1. 使设置列表可以滚动,并且点击列表外任意位置即可退出设置列表

  2. 增加 Telegram 用户白名单功能

  • 只有白名单内的 ID 可以正常访问 Bot
  • 其他用户输入内容则显示未验证,并显示用户 ID 以便其将自己的 ID 加入白名单
  • 可以在 WebUI 设置列表中修改白名单
  • 支持通过在白名单中添加 * 来使 Bot 对所有人开放(危险)
  • 如果需要关闭 Telegram Bot 功能,直接把 Bot token 改成错误值如 123456 即可

验证:

  • corepack pnpm run build:frontend
  • corepack pnpm run build:cli

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add Telegram user allowlist and improve thread persistence handling

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Restrict Telegram bridge to allowlisted users with configurable access control
• Delay stop button until new threads persist on server to prevent data loss
• Improve settings panel usability with scrolling and click-outside dismissal
• Add Telegram configuration UI for managing bot token and allowed user IDs
Diagram
flowchart LR
  A["Telegram Message"] --> B["Check Sender ID"]
  B --> C{Allowed?}
  C -->|Yes| D["Process Message"]
  C -->|No| E["Send Rejection with User ID"]
  F["New Thread Created"] --> G["Block Stop Button"]
  G --> H{Thread Persisted?}
  H -->|Yes| I["Unblock Stop Button"]
  J["Settings Panel"] --> K["Click Outside"]
  K --> L["Close Panel"]
Loading

Grey Divider

File Changes

1. src/api/codexGateway.ts ✨ Enhancement +43/-0

Add Telegram allowlist configuration API support

• Added allowedUsers and allowAllUsers fields to TelegramStatus type
• Created new TelegramConfig type with botToken and allowedUserIds fields
• Updated configureTelegramBot function to accept allowedUserIds parameter
• Added new getTelegramConfig API function to retrieve current Telegram configuration
• Enhanced getTelegramStatus to include allowlist information in response

src/api/codexGateway.ts


2. src/composables/useDesktopState.ts 🐞 Bug fix +93/-0

Implement thread persistence gate for stop button

• Added three new state refs to track thread persistence:
 interruptBlockedUntilPersistedByThreadId, threadListedByServerById,
 persistedUserMessageByThreadId
• Created isSelectedThreadInterruptPending computed property to check if stop is blocked
• Implemented blockInterruptUntilThreadIsPersisted to prevent stop until thread persists
• Implemented maybeUnblockInterruptForPersistedThread to unlock stop when persistence conditions
 met
• Added markServerListedThreads to track when threads appear in server response
• Added markThreadMessagesPersisted to track when user messages are persisted
• Updated interruptSelectedThreadTurn to check persistence gate before allowing interrupt
• Updated insertOptimisticThread to block interrupt until new thread persists

src/composables/useDesktopState.ts


3. src/server/codexAppServerBridge.ts ✨ Enhancement +47/-5

Add server-side Telegram allowlist persistence and API

• Extended TelegramBridgeConfigState to include allowedUserIds field
• Enhanced normalizeTelegramBridgeConfig to parse and validate allowed user IDs with support for
 * wildcard
• Updated config read/write functions to persist allowedUserIds
• Modified Telegram bridge initialization to configure allowed user IDs from config
• Added new /codex-api/telegram/config GET endpoint to retrieve current configuration
• Updated /codex-api/telegram/configure-bot POST endpoint to validate and persist allowed user IDs

src/server/codexAppServerBridge.ts


View more (5)
4. src/server/telegramThreadBridge.ts 🐞 Bug fix +87/-0

Implement Telegram sender authorization checks

• Added from field extraction from Telegram message and callback query updates
• Created NormalizedTelegramAllowlist type and normalizeTelegramAllowlist function for parsing
 allowlist
• Added allowAllUsers and allowedUserIds instance variables to track allowlist state
• Implemented configureAllowedUserIds method to update allowlist configuration
• Added isAllowedSender method to validate sender against allowlist
• Implemented unauthorizedMessage and unauthorizedCallbackMessage methods to inform rejected
 users of their ID
• Updated handleIncomingUpdate to check sender authorization before processing messages
• Updated handleCallbackQuery to check sender authorization before processing callbacks
• Enhanced TelegramBridgeStatus to include allowedUsers count and allowAllUsers flag

src/server/telegramThreadBridge.ts


5. src/App.vue ✨ Enhancement +200/-24

Add Telegram allowlist UI and improve settings panel

• Added refs for settings panel, area, and button to enable click-outside dismissal
• Implemented onDocumentPointerDown and onSettingsAreaClick handlers for closing settings on
 outside click
• Added Escape key handler to close settings panel
• Replaced simple prompt-based Telegram configuration with full UI panel
• Created new Telegram config form with bot token and allowed user IDs inputs
• Added refreshTelegramConfig function to load current configuration
• Implemented parseTelegramAllowedUserIdsInput to parse user input with flexible formatting
• Implemented saveTelegramConfig to validate and persist Telegram settings
• Updated telegramStatusText computed to display allowlist information
• Added isStopPending prop to ThreadComposer components
• Made settings panel scrollable with max height constraint

src/App.vue


6. src/components/content/ThreadComposer.vue ✨ Enhancement +10/-3

Add stop button pending state UI feedback

• Added isStopPending prop to component definition
• Updated stop button to show spinner when isStopPending is true
• Modified stop button disabled state to include isStopPending check
• Updated stop button aria-label and title to reflect pending state
• Added CSS for stop spinner animation

src/components/content/ThreadComposer.vue


7. README.md 📝 Documentation +9/-0

Document Telegram allowlist configuration

• Added TELEGRAM_ALLOWED_USER_IDS environment variable documentation
• Added instructions for finding Telegram user ID via bot API
• Documented that allowed user IDs are required for safe access
• Explained that unauthorized users receive rejection with their ID

README.md


8. tests.md 🧪 Tests +26/-3

Add Telegram allowlist testing documentation

• Updated Telegram configuration test to include allowed user IDs requirement
• Added verification step for allowedUserIds field in config file
• Added new test scenario for unauthorized sender rejection
• Documented expected behavior for allowlisted vs non-allowlisted accounts

tests.md


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Apr 12, 2026

Code Review by Qodo

🐞 Bugs (2)   📘 Rule violations (1)   📎 Requirement gaps (1)   🎨 UX Issues (0)
🐞\ ≡ Correctness (1) ⛨ Security (1)
📘\ ⚙ Maintainability (1)
📎\ ⛨ Security (1)

Grey Divider


Action required

1. * bypasses Telegram allowlist 📎
Description
The Telegram bridge treats * in allowedUserIds as allowAllUsers, which permits any Telegram
sender ID to pass validation. This violates the requirement to restrict bot access to an explicit
whitelist of authorized user IDs.
Code

src/server/telegramThreadBridge.ts[R73-96]

+type NormalizedTelegramAllowlist = {
+  allowAllUsers: boolean
+  allowedUserIds: number[]
+}
+
+function normalizeTelegramAllowlist(values: unknown): NormalizedTelegramAllowlist {
+  const rawValues = Array.isArray(values) ? values : []
+  const allowAllUsers = rawValues.some((value) => typeof value === 'string' && value.trim() === '*')
+  const allowedUserIds = Array.from(new Set(rawValues
+    .map((value) => {
+      if (typeof value === 'number' && Number.isFinite(value)) {
+        return Math.trunc(value)
+      }
+      if (typeof value === 'string' && value.trim().length > 0) {
+        const normalized = value.trim().replace(/^(telegram|tg):/i, '').trim()
+        if (/^-?\d+$/.test(normalized)) {
+          return Number.parseInt(normalized, 10)
+        }
+      }
+      return Number.NaN
+    })
+    .filter((value) => Number.isFinite(value)))).slice(0, 100)
+  return { allowAllUsers, allowedUserIds }
+}
Evidence
PR Compliance ID 2 requires restricting bot access to an explicit whitelist of authorized Telegram
user IDs. The code explicitly supports * to enable allowAllUsers, and isAllowedSender() then
accepts any finite sender ID when that flag is set.

Telegram bot access must be restricted to an explicit whitelist of authorized user IDs
src/server/telegramThreadBridge.ts[73-96]
src/server/telegramThreadBridge.ts[348-355]
src/App.vue[234-236]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Telegram allowlist supports `*` to allow all users, which conflicts with the compliance requirement to restrict access to explicit authorized Telegram user IDs.

## Issue Context
`normalizeTelegramAllowlist()` sets `allowAllUsers` when `*` is present, and `isAllowedSender()` then authorizes any finite Telegram sender ID.

## Fix Focus Areas
- src/server/telegramThreadBridge.ts[73-96]
- src/server/telegramThreadBridge.ts[348-355]
- src/App.vue[234-236]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. tests.md missing settings-panel test 📘
Description
The PR changes Settings panel behavior (scrollable panel plus close-on-outside-click/Escape) but
does not add corresponding manual test instructions in tests.md. This violates the requirement to
document manual tests for each implemented feature change.
Code

src/App.vue[R1800-1816]

+function onDocumentPointerDown(event: PointerEvent): void {
+  if (!isSettingsOpen.value) return
+  const target = event.target
+  if (!(target instanceof Node)) return
+  if (settingsPanelRef.value?.contains(target)) return
+  if (settingsButtonRef.value?.contains(target)) return
+  isSettingsOpen.value = false
+}
+
+function onSettingsAreaClick(event: MouseEvent): void {
+  if (!isSettingsOpen.value) return
+  const target = event.target
+  if (!(target instanceof Node)) return
+  if (settingsPanelRef.value?.contains(target)) return
+  if (settingsButtonRef.value?.contains(target)) return
+  isSettingsOpen.value = false
+}
Evidence
PR Compliance ID 3 requires tests.md updates for each implemented feature change. The PR adds
outside-click/Escape-close logic and makes the settings panel scrollable, but no new/updated
tests.md steps are provided to validate these behaviors.

AGENTS.md
src/App.vue[1244-1265]
src/App.vue[1787-1816]
src/App.vue[3362-3364]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`tests.md` is missing manual test instructions to verify the updated Settings panel usability (scrolling and closing by outside click / Escape).

## Issue Context
The PR adds new close-handlers and scroll styling for the Settings panel, which needs repeatable manual verification steps.

## Fix Focus Areas
- tests.md[1-40]
- src/App.vue[1787-1816]
- src/App.vue[3362-3364]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Allowlist config override bug 🐞
Description
createCodexBridgeMiddleware always overwrites the Telegram allowlist seeded from
TELEGRAM_ALLOWED_USER_IDS with the persisted config (including empty allowedUserIds from older
configs), which can unexpectedly block all Telegram users. This makes env-based allowlisting
ineffective whenever ~/.codex/telegram-bridge.json has a botToken but lacks allowedUserIds.
Code

src/server/codexAppServerBridge.ts[R2849-2853]

    .then((config) => {
      if (!config.botToken) return
      telegramBridge.configureToken(config.botToken)
+      telegramBridge.configureAllowedUserIds(config.allowedUserIds)
      telegramBridge.start()
Evidence
TelegramThreadBridge initializes its allowlist from TELEGRAM_ALLOWED_USER_IDS, but startup code
later unconditionally calls configureAllowedUserIds(config.allowedUserIds). Since
normalizeTelegramBridgeConfig defaults missing/invalid allowedUserIds to an empty array, upgrading
from an older telegram-bridge.json (botToken/chatIds only) will override the env allowlist with an
empty allowlist and reject everyone.

src/server/telegramThreadBridge.ts[113-123]
src/server/codexAppServerBridge.ts[1879-1905]
src/server/codexAppServerBridge.ts[1907-1915]
src/server/codexAppServerBridge.ts[2828-2854]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Telegram allowlist configured via `TELEGRAM_ALLOWED_USER_IDS` is overwritten at startup by the persisted config allowlist, even when that persisted allowlist is empty/missing (e.g., older `telegram-bridge.json`). This results in all senders being rejected and makes env-based allowlisting ineffective.

## Issue Context
`TelegramThreadBridge` constructor seeds the allowlist from `TELEGRAM_ALLOWED_USER_IDS`. Later, `createCodexBridgeMiddleware()` reads `~/.codex/telegram-bridge.json` and unconditionally applies `configureAllowedUserIds(config.allowedUserIds)`. The normalization currently turns missing `allowedUserIds` into `[]`, which overrides the env seed.

## Fix Focus Areas
- src/server/codexAppServerBridge.ts[2848-2854]
- src/server/codexAppServerBridge.ts[1879-1915]

## Implementation notes
- Only call `telegramBridge.configureAllowedUserIds(...)` during startup if the persisted config contains a non-empty allowlist.
- Alternatively, merge persisted allowlist with env allowlist, or treat missing `allowedUserIds` in persisted config as “unset” rather than `[]` (so env remains effective).
- Consider surfacing a clear status/error when token exists but allowlist is not configured.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Telegram token leakage endpoint 🐞
Description
The new GET /codex-api/telegram/config endpoint returns the Telegram bot token in plaintext, making
exfiltration trivial for any client that can access the web server. Since the server binds to
0.0.0.0 and the auth layer explicitly trusts Tailscale peers, this increases the blast radius of
token compromise beyond the local machine.
Code

src/server/codexAppServerBridge.ts[R3688-3696]

+      if (req.method === 'GET' && url.pathname === '/codex-api/telegram/config') {
+        const config = await readTelegramBridgeConfig()
+        setJson(res, 200, {
+          data: {
+            botToken: config.botToken,
+            allowedUserIds: config.allowedUserIds,
+          },
+        })
+        return
Evidence
The endpoint directly returns config.botToken over HTTP. The CLI listens on all interfaces
(0.0.0.0), and the auth logic allows requests from Tailscale remotes without a session token;
therefore any Tailscale peer (or anyone with access when password protection is disabled) can
retrieve the bot token once they can reach the service.

src/server/codexAppServerBridge.ts[3688-3696]
src/cli/index.ts[395-417]
src/server/authMiddleware.ts[63-82]
src/server/httpServer.ts[75-87]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`GET /codex-api/telegram/config` returns `botToken` in plaintext. This makes it easy for any network-accessible, authorized client to exfiltrate the Telegram bot token.

## Issue Context
The service can be reachable on the LAN/Tailscale (binds to `0.0.0.0`), and the auth layer explicitly trusts Tailscale remotes. Even with authentication, returning long-lived secrets via a simple GET endpoint increases exposure.

## Fix Focus Areas
- src/server/codexAppServerBridge.ts[3688-3696]
- src/api/codexGateway.ts[1763-1793]
- src/App.vue[1346-1397]

## Implementation notes
Choose one of:
- Return only non-sensitive fields (e.g., `allowedUserIds`, and `configured: boolean`), and require users to re-enter the bot token when updating.
- Return a masked token (e.g., last 4 chars) plus a boolean `hasToken`.
- Restrict the endpoint to localhost only (explicit remoteAddress check) if you must return the token.
Also update the UI to not expect the full token from the backend.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +73 to +96
type NormalizedTelegramAllowlist = {
allowAllUsers: boolean
allowedUserIds: number[]
}

function normalizeTelegramAllowlist(values: unknown): NormalizedTelegramAllowlist {
const rawValues = Array.isArray(values) ? values : []
const allowAllUsers = rawValues.some((value) => typeof value === 'string' && value.trim() === '*')
const allowedUserIds = Array.from(new Set(rawValues
.map((value) => {
if (typeof value === 'number' && Number.isFinite(value)) {
return Math.trunc(value)
}
if (typeof value === 'string' && value.trim().length > 0) {
const normalized = value.trim().replace(/^(telegram|tg):/i, '').trim()
if (/^-?\d+$/.test(normalized)) {
return Number.parseInt(normalized, 10)
}
}
return Number.NaN
})
.filter((value) => Number.isFinite(value)))).slice(0, 100)
return { allowAllUsers, allowedUserIds }
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. * bypasses telegram allowlist 📎 Requirement gap ⛨ Security

The Telegram bridge treats * in allowedUserIds as allowAllUsers, which permits any Telegram
sender ID to pass validation. This violates the requirement to restrict bot access to an explicit
whitelist of authorized user IDs.
Agent Prompt
## Issue description
The Telegram allowlist supports `*` to allow all users, which conflicts with the compliance requirement to restrict access to explicit authorized Telegram user IDs.

## Issue Context
`normalizeTelegramAllowlist()` sets `allowAllUsers` when `*` is present, and `isAllowedSender()` then authorizes any finite Telegram sender ID.

## Fix Focus Areas
- src/server/telegramThreadBridge.ts[73-96]
- src/server/telegramThreadBridge.ts[348-355]
- src/App.vue[234-236]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/App.vue
Comment on lines +1800 to +1816
function onDocumentPointerDown(event: PointerEvent): void {
if (!isSettingsOpen.value) return
const target = event.target
if (!(target instanceof Node)) return
if (settingsPanelRef.value?.contains(target)) return
if (settingsButtonRef.value?.contains(target)) return
isSettingsOpen.value = false
}

function onSettingsAreaClick(event: MouseEvent): void {
if (!isSettingsOpen.value) return
const target = event.target
if (!(target instanceof Node)) return
if (settingsPanelRef.value?.contains(target)) return
if (settingsButtonRef.value?.contains(target)) return
isSettingsOpen.value = false
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. tests.md missing settings-panel test 📘 Rule violation ⚙ Maintainability

The PR changes Settings panel behavior (scrollable panel plus close-on-outside-click/Escape) but
does not add corresponding manual test instructions in tests.md. This violates the requirement to
document manual tests for each implemented feature change.
Agent Prompt
## Issue description
`tests.md` is missing manual test instructions to verify the updated Settings panel usability (scrolling and closing by outside click / Escape).

## Issue Context
The PR adds new close-handlers and scroll styling for the Settings panel, which needs repeatable manual verification steps.

## Fix Focus Areas
- tests.md[1-40]
- src/App.vue[1787-1816]
- src/App.vue[3362-3364]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/server/codexAppServerBridge.ts
@kinogram
Copy link
Copy Markdown
Contributor Author

恳请开发者手动测试一下吧,如果没有问题就合并吧谢谢。我得去写作业了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants