Skip to content

fix(security): close Telegram-callback link CSRF and gate restricted supporters - #64

Merged
matthew-demidoff merged 2 commits into
masterfrom
fix/telegram-csrf-and-restricted-actions
Aug 20, 2026
Merged

fix(security): close Telegram-callback link CSRF and gate restricted supporters#64
matthew-demidoff merged 2 commits into
masterfrom
fix/telegram-csrf-and-restricted-actions

Conversation

@matthew-demidoff

Copy link
Copy Markdown
Collaborator

Closes two independently-reported High findings on privileged/auth paths, both verified against the code.

Telegram callback CSRF -> account takeover

GET /api/telegram/callback auto-linked a Telegram identity to the logged-in session user from a bare GET. A cross-site GET carries the victim's SameSite=Lax session cookie, so an attacker could bind their own Telegram id onto an unlinked victim, then replay the payload with no session to log in as the victim.

  • Linking now happens only through the /relink bot-approval flow, which binds the action to an interactive Approve in the user's own Telegram. The callback redirects a signed-in hitter there and never links.
  • The login branch single-uses the signed payload (Redis SET NX) so a captured payload cannot be replayed within its freshness window.
  • Removed the now-unused linkTelegram primitive so the vulnerable path cannot be reintroduced. The ban gate on the login branch is unchanged.

Restricted supporters retained privileged access

The security-review server actions gated only on support-team role, and server actions run before the (app) layout's restricted redirect, so a restricted security/security_high supporter could still restrict/unrestrict users, dismiss suspicion events, and post security replies. Each of the five actions now calls assertNotRestricted right after resolving the session, matching the pattern the actions had skipped.

Verification

typecheck clean; full suite green (241 passing) including new regressions: callback single-use replay rejection, signed-in -> /relink with no link, and restricted-supporter bounce on the privileged actions. Adversarially reviewed: restricted-supporter fix confirmed complete with no adjacent ungated surfaces (support actions already gated, admin actions gated by step-up).

Known latent gap (not introduced here, out of scope)

Removing the auto-link branch leaves no first-time Telegram-link path: /relink gates on an existing telegram_id. This affects nothing today because every account is created with Telegram linked (both registration paths require it) and there is no unlink feature, so no null-telegram account exists. If OAuth-only signup or an unlink feature is ever added, a CSRF-safe first-time-link flow must be built alongside it.

…supporters

Two independently-reported issues on privileged/auth paths:

- /api/telegram/callback auto-linked a Telegram identity to the logged-in
  session user from a bare GET. A cross-site GET carries the victim's
  SameSite=Lax cookie, so an attacker could bind their own Telegram id onto an
  unlinked victim, then replay the payload with no session to log in as the
  victim. Linking now goes only through the /relink bot-approval flow; the
  callback redirects a signed-in hitter there and never links. The login branch
  additionally single-uses the signed payload so a captured one cannot be
  replayed within its freshness window. Removed the now-unused linkTelegram
  primitive so the vulnerable path cannot be reintroduced.

- The security-review server actions (restrict/unrestrict, dismiss suspicion,
  security replies) gated only on support-team role, and server actions run
  before the (app) layout's restricted redirect, so a restricted security
  supporter could still drive the restriction console. Each action now calls
  assertNotRestricted right after resolving the session.
@matthew-demidoff
matthew-demidoff merged commit f890908 into master Aug 20, 2026
2 checks passed
@matthew-demidoff
matthew-demidoff deleted the fix/telegram-csrf-and-restricted-actions branch August 20, 2026 17:13
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