Skip to content

feat(security): webhook secret + per-user rate limit + 429 retry#25

Merged
heznpc merged 1 commit intomainfrom
feat/rate-limit-webhook-secret-retry-after
May 2, 2026
Merged

feat(security): webhook secret + per-user rate limit + 429 retry#25
heznpc merged 1 commit intomainfrom
feat/rate-limit-webhook-secret-retry-after

Conversation

@heznpc
Copy link
Copy Markdown
Member

@heznpc heznpc commented May 2, 2026

From the 2026-05-01 audit (P1.8). Three findings landed together because they share the message-handler call path.

Changes

  • Webhook secretWEBHOOK_SECRET env var, required when WEBHOOK_URL is set. bot.api.setWebhook({ secret_token }) + webhookCallback({ secretToken }) make grammy verify the X-Telegram-Bot-Api-Secret-Token header on every incoming request.
  • Per-user rate limit — wires existing src/lib/rate-limiter.js into handlers/echo.js (10 msg/min/user). Limited users are silently dropped (replying would burn outbound budget).
  • 429 / RetryAfter — new lib/safe-reply.js catches GrammyError 429, sleeps the suggested seconds, retries once, swallows other API/HTTP errors with structured logs.

Test plan

  • Local: 14 tests still passing
  • CI green
  • Manual: post a forged update without the header → 401 (after deploy)

…etry

Audit (2026-05-01) flagged three skin-deep bot patterns. All three now
land together because they share the per-message handler call site.

Webhook secret token (config + index)
  - WEBHOOK_SECRET env var. Required when WEBHOOK_URL is set; config.js
    rejects boot-time when one is set without the other.
  - .env.example documents the openssl rand -hex 32 generation.
  - bot.api.setWebhook() registers it with Telegram, webhookCallback()
    enforces the X-Telegram-Bot-Api-Secret-Token header. Forged updates
    from anyone who learns the URL are now rejected at the HTTP layer.

Per-user rate limit (handlers/echo.js)
  - Wires the existing src/lib/rate-limiter.js into the message handler
    (10 msg / minute / user). Limited users are silently dropped — we
    do NOT reply, since the reply itself burns Telegram outbound budget
    and amplifies the abuse.
  - Comment notes the in-memory limitation for sharded deployments.

Telegram 429 RetryAfter (lib/safe-reply.js)
  - New safeReply() helper centralises ctx.reply error handling: catches
    GrammyError 429, sleeps the server-suggested seconds, retries once,
    and gives up gracefully. Other GrammyError / HttpError swallowed
    with a structured log line.
  - echo.js now goes through safeReply() instead of a bare try/catch.
@heznpc heznpc merged commit 28ce7cf into main May 2, 2026
3 checks passed
@heznpc heznpc deleted the feat/rate-limit-webhook-secret-retry-after branch May 2, 2026 01:27
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.

1 participant