diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..cb4e245 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,120 @@ +# ⛔ ПРАВИЛА ДЛЯ COPILOT — ПРОЕКТ DRHIDER + +Читать перед ЛЮБЫМ действием. Нарушение = зря потраченное время. + +--- + +## ⛔⛔⛔ ЗОЛОТОЕ ПРАВИЛО + +**НИКОГДА НИЧЕГО НЕ ДЕЛАТЬ БЕЗ ЯВНОЙ КОМАНДЫ «ДЕЛАЙ».** + +Любой вопрос, обсуждение, анализ — НЕ повод менять код. +Только после явного: «делай», «делайте», «давай», «приступай», «пушить», «commit», «push». + +--- + +## ⛔⛔⛔ НЕ МЕНЯТЬ КОД БЕЗ «ДЕЛАЙ» + +Даже если ошибка очевидна. Даже если «исправление в одну строку». +**Показать проблему → описать решение → ЖДАТЬ «делай».** + +--- + +## ⛔⛔⛔ НЕ СМОТРЕТЬ ЛОКАЛЬНЫЕ ПАПКИ CONTRACTS + +Весь старый код drhider — ТОЛЬКО на ВМ (`5.172.178.213` через SSH). +**НЕ лезть** в `/home/naeel/nubes/contracts/`, `contracts-flask/` и т.д. + +Единственное исключение: `/home/naeel/nubes/loadtest/` (образец managed Flask). + +--- + +## ⛔⛔⛔ НЕ СПЕШИТЬ + +Сначала ВСЁ обдумать. Проверить. Перепроверить. Только потом отвечать. +Спешка → ошибки → проблемы. + +--- + +## ⛔⛔⛔ НЕ ПРЕДПОЛАГАТЬ, НЕ ДОГАДЫВАТЬСЯ + +Есть сомнения — **ОСТАНОВИТЬСЯ И СПРОСИТЬ**. +Не надо самостоятельно «улучшать» рабочий код без прямого разрешения. + +--- + +## ⛔⛔⛔ КОМАНДЫ В ТЕРМИНАЛЕ — С ТАЙМАУТАМИ + +- `curl`: `--max-time N` +- `ssh`: `-o ConnectTimeout=N` +- `grep` на больших файлах: `timeout N grep ...` + +--- + +## ✅ ПОСЛЕ КАЖДОЙ ПРАВКИ + +1. **Проверить синтаксис:** + ```bash + python3 -c "import py_compile; py_compile.compile('файл.py', doraise=True)" + ``` +2. **Проверить импорт работает:** + ```bash + cd /home/naeel/nubes/drhider && python3 -c "from drhider import obfuscate_files, LLMClient" + ``` +3. **Проверить соседние функции не затёрты** (после replace_string_in_file) +4. **СРАЗУ commit + push** с подробным сообщением + +--- + +## ✅ КОММИТИТЬ + ПУШИТЬ ПОСЛЕ КАЖДОЙ ПРАВКИ + +Без исключений. Никаких «потом», никаких накоплений. +```bash +git add -A +git commit -m "подробное описание что сделано" +git push +``` + +--- + +## ✅ ДОКУМЕНТИРОВАТЬ ВСЁ В History/ + +Каждое изменение, план, ошибку — в `/home/naeel/nubes/drhider/History/`: +- Отдельный `.md` файл на каждую сессию/задачу +- Что планировалось → что сделано → в чём ошибся + +--- + +## 📋 ПРОЕКТ DRHIDER — КЛЮЧЕВЫЕ ФАКТЫ + +| Факт | Значение | +|---|---| +| Платформа | **Штурвал** (Managed Flask на pythonk8s) | +| URL | https://drhider.pythonk8s.dev.nubes.ru/ | +| Репозиторий | https://gitea.services.ngcloud.ru/Nail/drhider | +| Точка входа | `site/app.py` → `app = create_app()` | +| БД | **НЕТ** (всё в памяти) | +| Dockerfile | **НЕТ** (Штурвал сам запускает Flask) | +| gunicorn | **НЕТ** (Штурвал сам) | +| Зависимости | `flask`, `python-docx`, `pdfplumber`, `httpx` | +| ВМ (legacy) | `5.172.178.213`, ssh-ключ `~/.ssh/naeel_vm_id_ed25519` | + +### Переменные окружения + +| Переменная | Значение | +|---|---| +| `LLM_API_KEY` | `sk-ucI5YvOticoOQ9Kuj5K9mQ` | +| `LLM_URL` | `https://api.aillm.ru/v1/chat/completions` | +| `LLM_MODEL` | `gpt-oss-120b` | + +--- + +## ⛔ ЗАПРЕЩЕНО + +1. Менять код без «делай» +2. Смотреть локальные папки contracts/ +3. Действовать по догадкам («я бы сделал так», «можно попробовать») +4. Удалять файлы/папки без разрешения +5. Использовать sed +6. Откладывать commit/push «на потом» +7. Игнорировать проверку синтаксиса перед push diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..756e02c --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,191 @@ +# Архитектура DrHider + +## Общая схема + +``` +Пользователь (браузер) + │ + ▼ +https://drhider.pythonk8s.dev.nubes.ru/ + │ + ▼ +Штурвал (Managed Flask на pythonk8s) + │ + ▼ +site/app.py ──→ Flask (create_app) + │ + ├── GET / → main_bp → templates/index.html + ├── GET /health → health_bp → {"ok": true} + └── POST /api/drhider → api_bp → drhider.obfuscate_files() + │ + ▼ + TwoPassObfuscator + │ + ┌────────────┼────────────┐ + │ │ │ + extractor scanner replacer + (текст) (regex+LLM) (замена) + │ │ │ + └────────────┼────────────┘ + │ + builder + (ZIP+CSV) + │ + ▼ + application/zip +``` + +--- + +## Двухпроходная обфускация + +### Проход 1: сбор сущностей + +``` +Файлы (.docx, .pdf, .txt, .zip) + │ + ▼ +extractor.expand_zips() ← распаковать ZIP + │ + ▼ +extractor.convert_pdfs_to_docx() ← PDF → DOCX + │ + ▼ +extractor.extract_text() ← извлечь текст из каждого файла + │ + ├──→ scanner.scan_regex() ← regex: телефоны, email, ИНН, ОГРН, КПП, + │ БИК, счета, паспорта, компании, ФИО + │ + └──→ scanner.scan_llm_ner() ← LLM: имена, адреса, паспорта + (только если llm_client передан) + │ + ▼ +mapping = {оригинал → фиктивное} ← глобальный словарь замен +``` + +### Проход 2: замена + +``` +mapping + sorted_keys (по убыванию длины) + │ + ▼ +Для каждого файла: + │ + ├── .docx → replacer.replace_in_docx() ← склеить runs → заменить → сохранить + ├── .doc → без изменений (бинарный) + └── .txt/.pdf → replacer.replace_in_text() ← простая строковая замена + │ + ▼ +builder.build_mapping_csv() ← mapping.csv +builder.build_zip() ← ZIP со всеми файлами + mapping.csv +``` + +--- + +## Модули + +### `drhider/config.py` +Константы уровня всего пакета. НЕ содержит логики — только данные. +- `ENTITY_PATTERNS` — 10 regex-паттернов +- `COMPANY_PATTERN`, `PERSON_PATTERN` — скомпилированные regex +- Словари: `RU_SURNAMES`, `RU_NAMES`, `RU_PATRONYMICS`, `RU_CITIES`, `RU_STREETS`, `FAKE_DOMAINS` + +### `drhider/checksum.py` +Чистые функции для расчёта контрольных сумм. Без побочных эффектов. +- `checksum_inn10(inn)` → 1 цифра +- `checksum_inn12(inn)` → 2 цифры +- `checksum_ogrn(ogrn)` → 1 цифра + +### `drhider/random_utils.py` +Утилиты генерации случайных строк. +- `random_digits(n)` → строка из n цифр +- `random_letters(n)` → строка из n строчных латинских букв + +### `drhider/generators/` +Каждый генератор — независимая функция с сигнатурой `generate_xxx(original: str) -> str`. +Параметр `original` нужен для извлечения префикса (например, «ИНН » из «ИНН 1234567890»). +Генераторы НЕ общаются друг с другом — только импортируют из `config`, `checksum`, `random_utils`. + +`__init__.py` содержит `ENTITY_GENERATORS` — словарь, связывающий `entity_type` из `ENTITY_PATTERNS` с функцией-генератором. Используется в `scanner.scan_regex()`. + +### `drhider/llm_client.py` +HTTP-клиент к OpenAI-совместимому API. Читает переменные окружения. +Интерфейс: `.complete(prompt: str) -> str`. + +### `drhider/extractor.py` +Три независимые функции (не класс): +- `extract_text(fname, content, ctype)` → `(text, docx_document_or_None)` +- `expand_zips(files)` → распакованный список +- `convert_pdfs_to_docx(files)` → PDF заменены на DOCX + +### `drhider/scanner.py` +Две функции (не класс), мутируют переданный `mapping: Dict[str, str]`: +- `scan_regex(text, mapping)` — regex-поиск +- `scan_llm_ner(all_texts, mapping, llm_client)` — LLM NER + +### `drhider/replacer.py` +Три чистые функции: +- `apply_replacements(text, mapping, sorted_keys)` → строка с заменами +- `replace_in_docx(doc, mapping, sorted_keys)` → bytes (изменённый DOCX) +- `replace_in_text(text, fname, mapping, sorted_keys)` → bytes + +### `drhider/builder.py` +Две функции сборки результата: +- `build_zip(files, mapping_csv)` → bytes (ZIP-архив) +- `build_mapping_csv(mapping)` → str (CSV) + +### `drhider/obfuscator.py` +`TwoPassObfuscator` — оркестратор. Единственный класс, который знает о顺序е вызовов. +Метод `.obfuscate(files)` вызывает модули в правильном порядке. +`obfuscate_files()` — удобная функция-обёртка. + +### `site/app.py` +Точка входа для Штурвала. `create_app()` создаёт Flask, регистрирует blueprint'ы. +`app = create_app()` — глобальный экземпляр. + +### `site/routes/` +Blueprint'ы — каждый в своём файле: +- `main_bp` — только `GET /` +- `health_bp` — только `GET /health` +- `api_bp` — только `POST /api/drhider` + +`__init__.py` содержит `register_routes(app)` — единая точка регистрации. + +--- + +## Поток данных + +``` +HTTP POST /api/drhider (multipart/form-data) + │ + ▼ +api_bp.drhider() + │ request.files.getlist("files") + │ → [(filename, bytes, mimetype), ...] + ▼ +obfuscate_files(files, llm_client=LLMClient()) + │ + ▼ +TwoPassObfuscator.obfuscate(files) + │ + ├── expand_zips → convert_pdfs_to_docx → extract_text + ├── scan_regex + scan_llm_ner → mapping + ├── replace_in_docx / replace_in_text → обфусцированные файлы + └── build_mapping_csv + build_zip → (zip_bytes, csv_str) + │ + ▼ +send_file(BytesIO(zip_data), mimetype="application/zip") + │ + ▼ +HTTP 200 + ZIP-архив +``` + +--- + +## Принципы + +1. **Модули — чистые функции.** Где возможно — без классов, без состояния. +2. **Единственный класс — TwoPassObfuscator.** Только он управляет состоянием (mapping, sorted_keys). +3. **Генераторы — изолированы.** Каждый в своём файле, не зависят друг от друга. +4. **Flask — тонкая прослойка.** Только принимает запрос, вызывает `obfuscate_files()`, отдаёт ответ. +5. **Без БД.** Всё в памяти. Никаких внешних зависимостей кроме LLM API. diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..06285f8 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,139 @@ +# Деплой, ВМ и переменные окружения + +--- + +## Платформа + +**Штурвал** — Managed Flask на pythonk8s (Kubernetes). + +- Запускает Flask **сам** — без Dockerfile, без gunicorn +- Точка входа: `site/app.py` → глобальная переменная `app` +- Деплой: `git push origin master` → авто-деплой через UI Штурвала + +--- + +## Деплой + +```bash +cd /home/naeel/nubes/drhider +git add -A +git commit -m "описание изменений" +git push origin master +``` + +После пуша — зайти в UI Штурвала и нажать ** Redeploy** для `drhider`. + +--- + +## Переменные окружения (настраиваются в UI Штурвала) + +| Переменная | Значение | Для чего | +|---|---|---| +| `LLM_API_KEY` | `sk-ucI5YvOticoOQ9Kuj5K9mQ` | Ключ доступа к LLM API | +| `LLM_URL` | `https://api.aillm.ru/v1/chat/completions` | URL LLM API | +| `LLM_MODEL` | `gpt-oss-120b` | Модель для NER | + +Без `LLM_API_KEY` LLM-сканирование не работает (только regex). + +--- + +## ВМ contracts (legacy) + +Старый сервер contracts.kube5s.ru. DrHider там — standalone HTTP-сервер на порту 8767. + +### SSH + +```bash +ssh -i ~/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213 +``` + +### Где лежит старый drhider + +``` +/home/naeel/contracts/drhider/ +├── drhider.py # Ядро обфускации (отсюда скопировано в этот проект) +├── drhider_server.py # HTTP-сервер :8767 (НЕ ИСПОЛЬЗУЕТСЯ в новом проекте) +└── __init__.py +``` + +### Сервисы на ВМ + +| Сервис | Порт | systemd unit | +|---|---|---| +| drhider | 8767 | `contracts-drhider` | +| convert_server | 8766 | `contracts` | +| Flask UI | 5001 | — (start.sh) | +| nginx | 443 | `nginx` | +| PostgreSQL | 5432 | — | + +### Управление drhider на ВМ + +```bash +# Статус +systemctl status contracts-drhider + +# Логи +journalctl -u contracts-drhider -f + +# Перезапуск +sudo systemctl restart contracts-drhider +``` + +--- + +## Локальная разработка + +```bash +cd /home/naeel/nubes/drhider + +# Установка зависимостей +pip install -r requirements.txt + +# Запуск Flask (dev-сервер) +cd site && python3 app.py + +# Проверка синтаксиса ВСЕХ файлов +python3 -c " +import py_compile, os +for root, dirs, files in os.walk('.'): + for f in files: + if f.endswith('.py'): + path = os.path.join(root, f) + py_compile.compile(path, doraise=True) + print(f'OK: {path}') +" + +# Проверка импорта +python3 -c "from drhider import obfuscate_files, LLMClient; print('OK')" +``` + +--- + +## Как править код + +1. Понять что меняем → **описать план** +2. Получить **«делай»** +3. Внести изменения +4. `python3 -c "import py_compile; py_compile.compile('файл.py', doraise=True)"` — КАЖДЫЙ изменённый .py +5. `python3 -c "from drhider import obfuscate_files, LLMClient"` — проверка импорта +6. `git add -A && git commit -m "подробно что и зачем" && git push` +7. Записать в `History/` что сделано +8. Зайти в UI Штурвала → Redeploy + +--- + +## Как добавить новый генератор + +1. Создать `drhider/generators/новый_тип.py` с функцией `generate_xxx(original: str) -> str` +2. Добавить regex-паттерн в `drhider/config.py` → `ENTITY_PATTERNS` +3. Добавить импорт и маппинг в `drhider/generators/__init__.py` → `ENTITY_GENERATORS` +4. Проверить синтаксис → commit → push → Redeploy + +--- + +## История версий + +| Версия | Дата | Что | +|---|---|---| +| 2.0.0 | 2026-07-12 | Модульный рефакторинг (22 модуля вместо монолита) | +| 1.0.0 | 2026-07-11 | Начальная версия (monolith drhider.py + Flask) | diff --git a/README.md b/README.md new file mode 100644 index 0000000..fed2d71 --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +# DrHider — обфускация документов (Managed Flask) + +**URL:** https://drhider.pythonk8s.dev.nubes.ru/ +**Репозиторий:** https://gitea.services.ngcloud.ru/Nail/drhider +**Платформа:** Штурвал (Managed Flask на pythonk8s) +**Дизайн фронтенда:** `/home/naeel/nubes/design/` + +--- + +## Что делает + +Загружаешь документы (.docx, .pdf, .txt, .zip) — получаешь ZIP с обфусцированными копиями. +Все персональные данные, реквизиты, телефоны, email заменяются на фиктивные. +В архиве — mapping.csv с таблицей замен (оригинал → фиктивное). + +Обфускация двухпроходная: +1. **Проход 1 (сбор):** regex + LLM находят все сущности во всех файлах → глобальный словарь замен +2. **Проход 2 (замена):** применяем замены ко всем файлам → ZIP + +Согласованность: одна и та же сущность во всех файлах → одно и то же фиктивное значение. + +--- + +## ⛔ ПРАВИЛА ДЛЯ АГЕНТОВ (читать перед ЛЮБЫМ действием) + +### 1. НЕ смотреть локальные папки contracts/contracts-flask +Весь старый код drhider — ТОЛЬКО на ВМ (`5.172.178.213`). НЕ локально. +Единственное исключение: `/home/naeel/nubes/loadtest/` (образец структуры managed Flask). + +### 2. Штурвал запускает Flask САМ +- БЕЗ Dockerfile (удалён) +- БЕЗ gunicorn (нет в requirements.txt) +- Точка входа: `site/app.py` → `app = create_app()` +- Деплой: `git push origin master` → авто-деплой в UI Штурвала + +### 3. НЕ менять код без «делай» +Даже если ошибка очевидна. Показать → описать → ЖДАТЬ. + +### 4. После ЛЮБОЙ правки: +```bash +python3 -c "import py_compile; py_compile.compile('файл.py', doraise=True)" +git add -A && git commit -m "подробное описание" && git push +``` + +### 5. Документировать ВСЁ в History/ +Каждое изменение, план, ошибку — в отдельный .md файл. + +--- + +## Структура проекта + +### `drhider/` — пакет ядра обфускации + +| Файл | Назначение | Строк | +|---|---|---| +| `__init__.py` | re-export: `obfuscate_files`, `LLMClient`, `TwoPassObfuscator` | 10 | +| `config.py` | Константы: regex-паттерны (ENTITY_PATTERNS, COMPANY_PATTERN, PERSON_PATTERN), словари имён/городов/улиц | 80 | +| `checksum.py` | Контрольные суммы: ИНН10, ИНН12, ОГРН | 60 | +| `random_utils.py` | Утилиты: `random_digits`, `random_letters` | 20 | +| `llm_client.py` | LLMClient — HTTP-клиент к LLM API (aillm.ru) | 50 | +| `extractor.py` | Извлечение текста из .docx/.pdf/.txt + `expand_zips` + `convert_pdfs_to_docx` | 180 | +| `scanner.py` | Проход 1: `scan_regex` (regex) + `scan_llm_ner` (LLM NER) | 110 | +| `replacer.py` | Проход 2: `apply_replacements`, `replace_in_docx`, `replace_in_text` | 100 | +| `builder.py` | Сборка: `build_zip`, `build_mapping_csv` | 65 | +| `obfuscator.py` | `TwoPassObfuscator` — оркестратор + `obfuscate_files()` | 100 | + +### `drhider/generators/` — генераторы фиктивных значений + +Каждый файл — один генератор (10–40 строк). Интерфейс: `generate_xxx(original: str) -> str`. + +| Файл | Что генерирует | +|---|---| +| `phone.py` | +7 (XXX) XXX-XX-XX | +| `email.py` | user@fake-domain | +| `inn.py` | ИНН 10 и 12 знаков (с контрольной суммой) | +| `ogrn.py` | ОГРН 13 знаков | +| `kpp.py` | КПП 9 знаков | +| `bik.py` | БИК 9 знаков (04XXXXXXX) | +| `accounts.py` | Расчётный счёт (40702...) + корр. счёт (30101...) | +| `passport.py` | Паспорт (XX XX XXXXXX) | +| `company.py` | ООО/ЗАО/АО «СлучайноеНазвание» | +| `person.py` | Фамилия И.О. | +| `address.py` | Город, ул. Улица, д. N | + +`__init__.py` содержит `ENTITY_GENERATORS` — маппинг `entity_type → генератор`. + +### `site/` — Flask-приложение + +| Файл | Назначение | +|---|---| +| `app.py` | `create_app()` — создание Flask, регистрация blueprint'ов, VERSION | +| `routes/__init__.py` | `register_routes(app)` | +| `routes/main_bp.py` | `GET /` — HTML-интерфейс | +| `routes/health_bp.py` | `GET /health` — `{"ok": true, "version": "..."}` | +| `routes/api_bp.py` | `POST /api/drhider` — multipart files → ZIP | +| `templates/index.html` | UI (выбор файлов, прогресс, скачивание) | +| `static/favicon.svg` | Иконка | + +### Корневые файлы + +| Файл | Назначение | +|---|---| +| `requirements.txt` | `flask`, `python-docx`, `pdfplumber`, `httpx` | +| `.gitignore` | `__pycache__/`, `*.pyc`, `.env` | +| `.gitea/workflows/deploy.yaml` | CI: валидация Python | +| `History/` | Документация всех изменений | +| `README.md` | Этот файл | +| `ARCHITECTURE.md` | Архитектура | +| `DEPLOY.md` | Деплой, ВМ, переменные окружения | + +--- + +## API + +| Метод | Путь | Что делает | +|---|---|---| +| `GET` | `/` | HTML-интерфейс | +| `GET` | `/health` | `{"ok": true, "version": "2.0.0"}` | +| `POST` | `/api/drhider` | multipart/form-data (поле `files`) → `application/zip` | + +--- + +## Переменные окружения (Штурвал) + +| Переменная | Значение | +|---|---| +| `LLM_API_KEY` | `sk-ucI5YvOticoOQ9Kuj5K9mQ` | +| `LLM_URL` | `https://api.aillm.ru/v1/chat/completions` | +| `LLM_MODEL` | `gpt-oss-120b` | + +--- + +## Связь с ВМ contracts (legacy) + +На ВМ `5.172.178.213` (contracts.kube5s.ru) есть старая версия drhider: +- `drhider_server.py` на порту 8767 — standalone HTTP-сервер +- `drhider.py` — та же логика, что и в этом проекте (скопирована оттуда) + +Этот проект (`drhider.pythonk8s.dev.nubes.ru`) — **независимый managed Flask**, заменяет ВМ-версию. + +SSH к ВМ: `ssh -i ~/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213`