Skip to content

fix: критические баги и дыры безопасности#55

Merged
fUS1ONd merged 10 commits into
mainfrom
fix/security-and-bugs-audit
Mar 24, 2026
Merged

fix: критические баги и дыры безопасности#55
fUS1ONd merged 10 commits into
mainfrom
fix/security-and-bugs-audit

Conversation

@xtclovver
Copy link
Copy Markdown
Collaborator

Summary

Полный аудит кодовой базы выявил критические проблемы безопасности и надёжности. Все исправления покрыты существующими тестами.

CRITICAL

  • SQLite WAL mode — без него concurrent writes (callback + scheduler + handler) вызывали database is locked
  • recover() во все горутины — один panic убивал весь процесс бота
  • Graceful shutdownbot.Stop() никогда не вызывался при SIGTERM, процесс зависал
  • Timing attack — секреты callback сравнивались через !=, заменено на subtle.ConstantTimeCompare

HIGH

  • Пагинация GetAllUsers — был хардкод 1000, пользователи 1001+ были невидимы для scheduler/broadcast
  • Chargeback не банил — мошенник мог вернуться по новому инвайту
  • Unbounded io.ReadAll — OOM при злонамеренном ответе внешних API
  • IdleTimeout на HTTP-сервере — защита от slowloris

MEDIUM

  • Moderator state handlers без проверки isModerator() — снятый модератор мог завершить операцию
  • HTML escape firstName — инъекция форматирования в уведомлении админу

Test plan

  • make fmt — без ошибок
  • make tests — все тесты зелёные
  • Тесты пагинации обновлены под новый формат URL

🤖 Generated with Claude Code

- SQLite WAL mode для корректной работы concurrent writes
- recover() во все горутины для предотвращения краша бота
- Graceful shutdown: bot.Stop() при SIGTERM
- subtle.ConstantTimeCompare для секретов callback (timing attack)
- Пагинация GetAllUsers (тикающая бомба при >1000 юзеров)
- Бан при chargeback (предотвращение мошенничества)
- io.LimitReader на ответы внешних API (OOM защита)
- IdleTimeout на HTTP-сервере (slowloris защита)
- Проверка isModerator в mod state handlers
- HTML escape firstName в уведомлении админу

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3b56b69407

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/bot/payment.go Outdated
Comment on lines +424 to +427
// Банём пользователя — chargeback = мошенничество, повторная регистрация запрещена
if err := h.bot.db.BanUser(payment.TelegramID, 0); err != nil {
slog.Warn("Chargeback: не удалось забанить пользователя", "error", err, "telegram_id", payment.TelegramID)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Apply full ban cleanup in the chargeback path

Calling BanUser here does not mirror the existing admin-ban flow in processBanUser, which also deletes the Remnawave account and the local users row. Because the only general ban gate is in handleStart (internal/bot/handlers.go:155), a chargebacked user who already has the chat open can still use regular handlers such as handlePayButton (internal/bot/payment_handler.go:19) against the still-present account. If chargeback is meant to be a permanent ban, this path needs the same cleanup as a normal ban.

Useful? React with 👍 / 👎.

Comment thread internal/bot/payment.go Outdated
Comment on lines +425 to +426
if err := h.bot.db.BanUser(payment.TelegramID, 0); err != nil {
slog.Warn("Chargeback: не удалось забанить пользователя", "error", err, "telegram_id", payment.TelegramID)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Propagate ban persistence failures during chargebacks

If BanUser fails here (for example because SQLite is temporarily locked), the handler only logs a warning and still returns success to Platega. That means the callback will not be retried even though the security action failed, so the user stays unbanned while the admin alert says they were banned. This should fail the callback the same way the preceding UpdatePaymentStatus call does.

Useful? React with 👍 / 👎.

Comment thread cmd/bot/main.go Outdated
Comment on lines +125 to +127
go func() {
defer recoverGoroutine("scheduler")
telegramBot.StartScheduler(ctx)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restart or fail hard when the scheduler goroutine panics

The new recoverGoroutine wrapper only logs and returns. If StartScheduler panics once because of a malformed DB/API record, this goroutine exits permanently while the bot keeps serving Telegram requests, so subscription expiry handling, auto-kicks, and confirmed_not_activated retries stop until someone manually restarts the process. The same pattern is also used for the sync loop and alerter.

Useful? React with 👍 / 👎.

@xtclovver
Copy link
Copy Markdown
Collaborator Author

@codex review last commit

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@xtclovver
Copy link
Copy Markdown
Collaborator Author

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Да иди нахуй

xtclovver and others added 2 commits March 24, 2026 01:49
- shutdownCh в тестовых хелперах setupTestBot/setupSchedulerTestBot (утечка горутин)
- done-канал в rate limiter'ах для корректного shutdown cleanup-горутин
- исключить chargebacked из CountFirstPaymentsByMonth (консистентность с остальными revenue-запросами)
- idempotency guard в handleChargeback (защита от повторной обработки при retry)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fUS1ONd
Copy link
Copy Markdown
Owner

fUS1ONd commented Mar 24, 2026

@codex review last commit

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6ef93febc

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/bot/payment.go Outdated
Comment on lines 421 to 425
if payment.Status == "chargebacked" {
return nil
}

if err := h.bot.db.UpdatePaymentStatus(payment.ID, "chargebacked"); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep chargeback retries executing side effects

This guard makes retries ineffective after partial failures in the same handler. The code marks the payment as chargebacked and later returns an error if BanUser fails, but the next callback retry will reload the payment and immediately exit at payment.Status == "chargebacked", so ban/removal side effects are never retried. In that scenario, the payment remains chargebacked while the user may stay unbanned and undeleted.

Useful? React with 👍 / 👎.

Comment thread internal/bot/scheduler.go
Comment on lines +318 to 321
// НЕ делаем return — продолжаем удаление из БД, чтобы избежать partial failure
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Abort local cleanup when Remnawave auto-kick deletion fails

Removing the early return here causes local user cleanup to proceed even when DeleteUser in Remnawave failed with a non-404 error. Since scheduler processing only continues for users still present in the bot DB (runSubscriptionSchedulerPass maps DB users first), this makes the failed panel deletion non-recoverable by future scheduler runs and can leave orphaned accounts in Remnawave indefinitely.

Useful? React with 👍 / 👎.

@fUS1ONd fUS1ONd merged commit 206a168 into main Mar 24, 2026
5 checks passed
@xtclovver xtclovver deleted the fix/security-and-bugs-audit branch March 24, 2026 14:07
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