From 94e3d05e45f69b47a7864ba6aa2122249058a35f Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Wed, 15 Apr 2026 13:11:22 -0700 Subject: [PATCH] fix(security): gate /channels/discover behind AdminAuth (#250) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #250 (MEDIUM). POST /channels/discover was on the open router and accepted an arbitrary Telegram bot token, turning it into: 1. A free bot-token validity oracle — attackers can enumerate/probe tokens at zero cost 2. A drive-by deleteWebhook side effect — every call invokes tgbotapi.DeleteWebhookConfig against the target bot, breaking legitimate webhook delivery 3. A rate-limit amplifier — getMe + deleteWebhook + getUpdates per call Fix: one-line addition of middleware.AdminAuth(db.DB) to the route, matching its actual intent (platform-operator admin helper, not a per-workspace route). Pattern mirrors /admin/liveness, /events, and /bundles/export from PR #167. No new test: AdminAuth behavior is covered by wsauth_middleware_test.go; this PR only wires it onto an additional route. The load-bearing code comment references #250 so future reviewers can't revert without an issue citation. Co-Authored-By: Claude Opus 4.6 (1M context) --- platform/internal/router/router.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/platform/internal/router/router.go b/platform/internal/router/router.go index 9db76af4..99e226c5 100644 --- a/platform/internal/router/router.go +++ b/platform/internal/router/router.go @@ -381,7 +381,13 @@ func Setup(hub *ws.Hub, broadcaster *events.Broadcaster, prov *provisioner.Provi wsAuth.DELETE("/channels/:channelId", chh.Delete) wsAuth.POST("/channels/:channelId/send", chh.Send) wsAuth.POST("/channels/:channelId/test", chh.Test) - r.POST("/channels/discover", chh.Discover) + // #250: /channels/discover is an admin-setup helper (takes a bot + // token, asks the vendor "what chats is this token a member of?"). + // Leaving it unauthenticated turned it into a bot-token oracle plus + // a drive-by deleteWebhook side effect against any valid token an + // attacker could probe. AdminAuth matches the intent — it's a + // platform-operator helper, not a per-workspace route. + r.POST("/channels/discover", middleware.AdminAuth(db.DB), chh.Discover) r.POST("/webhooks/:type", chh.Webhook) // WebSocket