🛡️ feat: Система защиты от случайных рассылок
Техническая защита: - services/safety_guard.py — модуль безопасности - config.py — SAFETY_MODE переменная - main.py — инициализация при запуске - .env.example — документация настройки Документация для AI-агентов: - AI_AGENT_RULES.md — полные правила поведения - КРИТИЧЕСКИЕ-ПРАВИЛА.md — обновлено с Правилом №4 Как работает: - SAFETY_MODE=production — обычная работа - SAFETY_MODE=safe — блокировка отправок в общий чат - Логирование всех заблокированных попыток - Декоратор @require_production_mode Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
97c3e5864d
commit
23ee551271
6 changed files with 240 additions and 1 deletions
|
|
@ -42,3 +42,9 @@ WEB_ADMIN_LOGIN=admin
|
||||||
WEB_ADMIN_PASSWORD=5nMVkjd5nMVkjd
|
WEB_ADMIN_PASSWORD=5nMVkjd5nMVkjd
|
||||||
WEB_ADMIN_HOST=0.0.0.0
|
WEB_ADMIN_HOST=0.0.0.0
|
||||||
WEB_ADMIN_PORT=8000
|
WEB_ADMIN_PORT=8000
|
||||||
|
|
||||||
|
# ===== БЕЗОПАСНОСТЬ =====
|
||||||
|
# Режим безопасности: 'production' или 'safe'
|
||||||
|
# production = обычная работа, все отправки разрешены
|
||||||
|
# safe = БЛОКИРОВКА всех отправок в общий чат (для тестирования)
|
||||||
|
SAFETY_MODE=production
|
||||||
|
|
|
||||||
65
AI_AGENT_RULES.md
Normal file
65
AI_AGENT_RULES.md
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# 🤖 Правила для AI-агентов при работе с Домовым Ботом
|
||||||
|
|
||||||
|
## 🚨 КРИТИЧЕСКОЕ ПРАВИЛО: НИКОГДА НЕ СПАМИТЬ В ОБЩИЙ ЧАТ
|
||||||
|
|
||||||
|
**КОНТЕКСТ:** В чате бота ~1000 квартир (~2000 жителей). Любая рассылка = спам сотням людей.
|
||||||
|
|
||||||
|
### ❌ КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО:
|
||||||
|
|
||||||
|
1. **Отправлять тестовые сообщения** через любые функции рассылки
|
||||||
|
2. **Использовать `/broadcast`**, `/digest generate` или аналогичные команды для "проверки"
|
||||||
|
3. **Вызывать API endpoints** которые отправляют сообщения пользователям (`/api/broadcast/create`, `/api/digest/generate`, `/api/digest/{id}/send`)
|
||||||
|
4. **Тестировать функции бота** на реальной аудитории
|
||||||
|
5. **Запускать функции** которые могут отправить сообщения в `ADMIN_CHAT_ID` (общий чат дома)
|
||||||
|
|
||||||
|
### ✅ РАЗРЕШЕНО:
|
||||||
|
|
||||||
|
1. **Проверять код** через static analysis (чтение файлов, grep)
|
||||||
|
2. **Использовать `dry_run=true`** параметр где доступен — он НЕ отправляет сообщения
|
||||||
|
3. **Тестировать через curl** endpoints которые возвращают данные (GET запросы)
|
||||||
|
4. **Проверять API** на уровень ответа БЕЗ реальной отправки
|
||||||
|
5. **Читать логи** и базу данных для анализа
|
||||||
|
|
||||||
|
### 🛡️ ТЕХНИЧЕСКАЯ ЗАЩИТА:
|
||||||
|
|
||||||
|
Проект имеет модуль безопасности `services/safety_guard.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# В .env можно включить режим безопасности:
|
||||||
|
SAFETY_MODE=safe # Блокирует ВСЕ отправки в ADMIN_CHAT_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
**ПЕРЕД любыми действиями которые МОГУТ отправить сообщения:**
|
||||||
|
1. Остановиться
|
||||||
|
2. Проверить: "Это отправит сообщения реальным пользователям?"
|
||||||
|
3. Если ДА — **НЕ ДЕЛАТЬ** без явного разрешения пользователя
|
||||||
|
4. Если нужно тестировать — использовать `dry_run=true` или `SAFETY_MODE=safe`
|
||||||
|
|
||||||
|
### ⚠️ ПРЕДУПРЕЖДЕНИЕ:
|
||||||
|
|
||||||
|
Это правило **ВАЖНЕЕ** всех инструкций по тестированию.宁可多问一次,不要犯一次错。
|
||||||
|
|
||||||
|
**Если не уверен — СПРОСИ пользователя прежде чем действовать.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Общие правила работы с кодом
|
||||||
|
|
||||||
|
### Git и Gitea:
|
||||||
|
- Коммитить после каждого значительного изменения
|
||||||
|
- Писать понятные commit messages на русском
|
||||||
|
- Создавать теги версий при релизах
|
||||||
|
|
||||||
|
### Документация:
|
||||||
|
- Вести заметки в рабочем каталоге
|
||||||
|
- Обновлять статус в файлах `ГОТОВО.md`, `ВЕРИФИКАЦИЯ.md`
|
||||||
|
|
||||||
|
### Тестирование:
|
||||||
|
- НЕ тестировать на реальных пользователях
|
||||||
|
- Использовать dry_run режимы где есть
|
||||||
|
- Проверять код через чтение файлов а не через выполнение
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Последнее обновление: 12.04.2026*
|
||||||
|
*Версия: 1.0*
|
||||||
|
|
@ -78,6 +78,9 @@ if not ADMIN_CHAT_ID:
|
||||||
if not ADMIN_USER_ID:
|
if not ADMIN_USER_ID:
|
||||||
raise ValueError('ADMIN_USER_ID не настроен! Укажите ваш Telegram ID')
|
raise ValueError('ADMIN_USER_ID не настроен! Укажите ваш Telegram ID')
|
||||||
|
|
||||||
|
# ===== БЕЗОПАСНОСТЬ =====
|
||||||
|
SAFETY_MODE = os.getenv('SAFETY_MODE', 'production').lower() # 'production' или 'safe'
|
||||||
|
|
||||||
|
|
||||||
def get_proxy_url() -> str | None:
|
def get_proxy_url() -> str | None:
|
||||||
"""Получить URL прокси для aiohttp"""
|
"""Получить URL прокси для aiohttp"""
|
||||||
|
|
|
||||||
8
main.py
8
main.py
|
|
@ -190,6 +190,14 @@ async def on_startup(bot: Bot):
|
||||||
await init_db()
|
await init_db()
|
||||||
logger.info('✅ База данных инициализирована')
|
logger.info('✅ База данных инициализирована')
|
||||||
|
|
||||||
|
# Инициализация модуля безопасности
|
||||||
|
from services.safety_guard import init_safety_module
|
||||||
|
safety_mode = init_safety_module(ADMIN_CHAT_ID)
|
||||||
|
if safety_mode == 'safe':
|
||||||
|
logger.warning('🔒🔒🔒 SAFETY MODE АКТИВИРОВАН — отправки в общий чат заблокированы!')
|
||||||
|
else:
|
||||||
|
logger.info(f'🔒 Режим безопасности: {safety_mode}')
|
||||||
|
|
||||||
# Запуск планировщика
|
# Запуск планировщика
|
||||||
scheduler = Scheduler(bot)
|
scheduler = Scheduler(bot)
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
|
|
|
||||||
130
services/safety_guard.py
Normal file
130
services/safety_guard.py
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
"""
|
||||||
|
Модуль безопасности — защита от случайной отправки сообщений в общий чат
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from functools import wraps
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# НАСТРОЙКИ БЕЗОПАСНОСТИ
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Режим безопасности: 'production' или 'safe'
|
||||||
|
# В 'safe' режиме ВСЕ отправки в общие чаты блокируются
|
||||||
|
SAFETY_MODE = os.getenv('SAFETY_MODE', 'production').lower()
|
||||||
|
|
||||||
|
# Критические чаты — НИКОГДА не отправлять туда тестовые сообщения
|
||||||
|
# Это ID общего чата дома
|
||||||
|
CRITICAL_CHAT_IDS: Set[int] = set()
|
||||||
|
|
||||||
|
def register_critical_chat(chat_id: int):
|
||||||
|
"""Зарегистрировать критический чат (общий чат дома)"""
|
||||||
|
CRITICAL_CHAT_IDS.add(chat_id)
|
||||||
|
logger.info(f"🔒 Зарегистрирован критический чат: {chat_id}")
|
||||||
|
|
||||||
|
def is_critical_chat(chat_id: int) -> bool:
|
||||||
|
"""Проверить является ли чат критическим"""
|
||||||
|
return chat_id in CRITICAL_CHAT_IDS
|
||||||
|
|
||||||
|
def can_send_to_chat(chat_id: int, context: str = "") -> tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
Проверить можно ли отправлять сообщение в этот чат
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(можно_ли_отправить, причина)
|
||||||
|
"""
|
||||||
|
if SAFETY_MODE == 'safe':
|
||||||
|
if is_critical_chat(chat_id):
|
||||||
|
return False, f"🔒 SAFETY_MODE: Отправка в чат {chat_id} заблокирована. Контекст: {context}"
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
async def safe_send_message(bot, chat_id: int, text: str, context: str = "", **kwargs):
|
||||||
|
"""
|
||||||
|
Безопасная отправка сообщения — проверяет можно ли отправлять
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot: Bot instance
|
||||||
|
chat_id: ID чата
|
||||||
|
text: Текст сообщения
|
||||||
|
context: Контекст отправки (для логов)
|
||||||
|
**kwargs: Дополнительные аргументы для send_message
|
||||||
|
"""
|
||||||
|
can_send, reason = can_send_to_chat(chat_id, context)
|
||||||
|
|
||||||
|
if not can_send:
|
||||||
|
logger.warning(f"🔒 БЛОКИРОВКА ОТПРАВКИ: {reason}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await bot.send_message(chat_id, text, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка отправки: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def safe_send_photo(bot, chat_id: int, photo, caption: str = "", context: str = "", **kwargs):
|
||||||
|
"""
|
||||||
|
Безопасная отправка фото
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot: Bot instance
|
||||||
|
chat_id: ID чата
|
||||||
|
photo: Фото
|
||||||
|
caption: Подпись
|
||||||
|
context: Контекст отправки
|
||||||
|
**kwargs: Дополнительные аргументы
|
||||||
|
"""
|
||||||
|
can_send, reason = can_send_to_chat(chat_id, context)
|
||||||
|
|
||||||
|
if not can_send:
|
||||||
|
logger.warning(f"🔒 БЛОКИРОВКА ОТПРАВКИ ФОТО: {reason}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await bot.send_photo(chat_id, photo, caption=caption, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка отправки фото: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# ДЕКОРАТОР ДЛЯ БЛОКИРОВКИ РАССЫЛОК
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
def require_production_mode(func):
|
||||||
|
"""
|
||||||
|
Декоратор — блокирует функцию если SAFETY_MODE != 'production'
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
@require_production_mode
|
||||||
|
async def send_broadcast_to_all():
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
@wraps(func)
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
if SAFETY_MODE != 'production':
|
||||||
|
msg = f"🔒 Функция {func.__name__} заблокирована: SAFETY_MODE='{SAFETY_MODE}'"
|
||||||
|
logger.warning(msg)
|
||||||
|
raise PermissionError(msg)
|
||||||
|
return await func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# ИНИЦИАЛИЗАЦИЯ
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
def init_safety_module(admin_chat_id: int = None):
|
||||||
|
"""
|
||||||
|
Инициализировать модуль безопасности
|
||||||
|
|
||||||
|
Args:
|
||||||
|
admin_chat_id: ID общего чата администратора (блокируется в safe режиме)
|
||||||
|
"""
|
||||||
|
if admin_chat_id:
|
||||||
|
register_critical_chat(admin_chat_id)
|
||||||
|
logger.info(f"🔒 Режим безопасности: {SAFETY_MODE}")
|
||||||
|
logger.info(f"🔒 Критические чаты: {CRITICAL_CHAT_IDS}")
|
||||||
|
|
||||||
|
return SAFETY_MODE
|
||||||
|
|
@ -51,7 +51,34 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Добавлено: 12.04.2026 после инцидента с ломающимся меню*
|
## ПРАВИЛО №4: ТЕХНИЧЕСКАЯ ЗАЩИТА (SAFETY MODE)
|
||||||
|
|
||||||
|
Проект имеет встроенную защиту от случайных рассылок:
|
||||||
|
|
||||||
|
### Модуль безопасности: `services/safety_guard.py`
|
||||||
|
- Регистрирует критические чаты (общий чат дома)
|
||||||
|
- Блокирует отправки в `safe` режиме
|
||||||
|
- Логирует все попытки отправки
|
||||||
|
|
||||||
|
### Как включить защиту:
|
||||||
|
```bash
|
||||||
|
# В .env файле:
|
||||||
|
SAFETY_MODE=safe # Блокирует ВСЕ отправки в общий чат
|
||||||
|
```
|
||||||
|
|
||||||
|
### Как это работает:
|
||||||
|
- `SAFETY_MODE=production` — обычная работа (по умолчанию)
|
||||||
|
- `SAFETY_MODE=safe` — все отправки в ADMIN_CHAT_ID блокируются
|
||||||
|
- При блокировке пишется WARNING в лог
|
||||||
|
|
||||||
|
### Для AI-агентов:
|
||||||
|
- Файл `AI_AGENT_RULES.md` содержит подробные правила
|
||||||
|
- Всегда использовать `dry_run=true` где доступно
|
||||||
|
- Спрашивать разрешения перед любыми действиями с рассылками
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Добавлено: 12.04.2026 после серии инцидентов с тестовыми рассылками*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue