Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
16 KiB
Что клиент получает вместе с продуктом и что из этого читается (аудит 18.09.2026)
Вопрос: клиент получает bj и может отдать его на разбор — человеку или нейросети.
Насколько легко по дистрибутиву восстановить, как продукт устроен, и повторить его?
Ниже — не рассуждения о концепции, а проверка реальных файлов: /opt/bj/bj-server
(сборка 18.09), crypto-service.jar, архивы кабинета, скрипты, образ установки.
Как собирается
Сборка правильная: go build -trimpath -ldflags "-s -w" для всех бинарей — таблица
символов и отладочная информация убраны, локальные пути машины сборки не попадают.
Но у Go это не даёт того, что даёт у C: имена функций остаются в таблице
pclntab, без них не работают паники и сборщик мусора. В боевом bj-server:
Что читается прямым strings |
Сколько |
|---|---|
имена функций пакета lkgateway |
4 672 |
пути исходников вида Bridge-and-Join-s/internal/…/файл.go |
4 931 |
SQL-запросы (SELECT …) |
214 |
таблицы учётных систем OD_*, D_B_*, FTV_M2M* |
32 уникальных |
| упоминания типов сообщений НРД (M2M01…, ORDXM, MF010, MS010, TransferDecision…) | 173 |
| фрагменты HTML-шаблонов | 739 |
| русский текст (подсказки, справка, тексты интерфейса) | ≈190 000 символов |
Имена функций и путей — это готовая карта проекта: lkgateway.lkArchivePortfolio,
lkgateway.spectreProceeds, lkgateway.ApplyDecision, internal/lkgateway/lkflow.go.
С такой картой декомпилятор (Ghidra с Go-плагином) восстанавливает структуру за часы,
а нейросети достаточно скормить вывод strings.
Что именно утекает и насколько это ценно
1. Справка внутри бинаря — самая большая дыра. В bj-server вшиты десять страниц
/admin/help/* общим объёмом ≈150 КБ: «Архитектура: как устроен обмен с НРД» (со
схемой контуров и адресами edog.nsd.ru/WslService), «Для разработчика Fancy»
(41 КБ — полное описание протокола событий FTV_M2M, таблиц, идентификаторов и порядка
этапов, «согласовано 10.09.2026»), QUIK, база, криптосервис, робот, ручной ввод.
Это не код — это проектная документация в чистом виде. Её не нужно даже
декомпилировать: strings bj-server | grep -A50 'Для разработчика Fancy'.
2. SQL к DEPO и SPECTRE. Все 214 запросов лежат текстом: какие таблицы, какие
связи (OD_RESTS.REG_3 = D_B_CONTRACTS.DOC, PART=39, счета 58.01/58.05, ВЫРУЧКА),
какие поля значат что. Это месяцы разбора чужой учётной системы, сложенные в один
grep. Именно эта часть — знание, которого нет ни в документации Fancy, ни в открытых
источниках.
3. Форматы НРД. XML собирается кодом из структур (encoding/xml), поэтому готовых
шаблонов в бинаре нет, но имена полей и типов сообщений видны все. Сами форматы —
публичная документация НРД, здесь утекает не формат, а знание, какие поля и как
заполнять (префиксы req:/typ:, <package>K</package>, порядок 16/1 → MS010).
4. crypto-service.jar — Java. Байт-код декомпилируется в почти исходник любым
JD-GUI/CFR. Наших классов немного (ru.zetit.bridgeandjoins.crypto.*, ~15 классов —
обёртка над Валидатой и gRPC), остальное — библиотеки. Утечка умеренная: это клей.
5. Скрипты и ретранслятор. Bash-помощники (bj-keymedia-*, 510 строк), PHP-ретранслятор
кабинета (629 строк), install.sh, bj-firstboot — исходники по природе. Ценности не
несут: тонкие обёртки. Это нормально.
6. Схема нашей базы (migrations/m2m-core/*.sql, 12 КБ) — вшита. Мелочь.
Что клиенту НЕ уходит и правильно: сервер лицензий (код, ключи подписи релизов и лицензий), портал, артефактория. Кабинет клиента в варианте В — образец правильного устройства: на хостинг уходит ретранслятор без логики, всё содержимое собирает ядро.
Лицензия как защита
Ключ подписан Ed25519, проверяется внутри бинаря; признаки модулей спрашиваются в
9 местах (moduleAllowed). Ядро без лицензии продолжает работать (решение 16.09:
переводы не должны встать из-за оплаты). Это значит: лицензия сегодня управляет
удобством (модули, обновления), а не возможностью работать. Обойти проверку в
Go-бинаре — поменять один условный переход; сервер об этом не узнает, потому что
единственное, что требует токен на сервере, — скачивание обновлений.
Модульность — про что она
Разделение на bj-server / bj-npr / bj-quikexport / bj-lk сделано для лицензирования и раздельного обновления. Защитой знаний оно не является: каждый бинарь несёт своё ноу-хау в том же читаемом виде, а ядро — ещё и разделы интерфейса всех модулей. Ожидать от модульности защиты не нужно; защищать надо содержимое бинарей.
Честная оценка риска
Полностью закрыться нельзя: продукт работает у клиента, на его машине, с его базами —
всё, что исполняется там, в принципе читаемо. Конкурент с доступом к Fancy и документам
НРД повторит продукт и без нашего бинаря, вопрос только времени. Поэтому цель защиты —
не сделать невозможным, а сделать дорогим и доказуемым: чтобы strings не выдавал
проектную документацию, чтобы декомпиляция стоила недель, а не часов, и чтобы факт
разбора был нарушением договора с понятными последствиями.
Что делать — по отдаче на вложенное
- Вынести справку из клиентской сборки (день). Страницы
/admin/help/*собирать только в сборку для нас (build tagdev) либо отдавать с портала по лицензии. Убирает самую крупную и самую дешёвую утечку. Риска для клиентов нет: справка нужна внедренцу, а не оператору. - Обфускация Go-сборок garble (день с прогоном тестов).
garble -literals -tiny build: имена функций и пакетов заменяются на случайные, строковые литералы (SQL, тексты) шифруются и расшифровываются только при выполнении —stringsперестаёт что-либо давать, декомпилятор получает безымянную кашу. Seed храним у себя (~/keys), паники с клиента разворачиваемgarble reverse. Цена: сборка медленнее, трассы с клиента нечитаемы без seed. - Договор. Лицензионное соглашение: запрет декомпиляции, реверса, передачи третьим лицам и загрузки в сторонние сервисы (включая ИИ), ноу-хау как коммерческая тайна, неустойка. Принятие — галочкой на портале перед скачиванием, с записью кто/когда/что скачал. Это единственное, что работает после утечки.
- Метка сборки. Портал уже знает лицензию скачивающего: при выдаче дистрибутива
кабинета и пакетов обновления вшивать идентификатор лицензии в архив/бинарь
(
-Xпри сборке или запись в служебный раздел). Утёкший файл указывает на источник. Для общих релизов из артефактории — подпись манифеста уже привязана к токену, добавить журнал выдачи достаточно. - Java-сервис: ProGuard/R8 для своих классов (полдня) — либо в перспективе перенести подпись в Go, служба маленькая.
- Не делать: переносить логику работы с DEPO/НРД «в облако» — продукт обязан работать в закрытом контуре без нас; это его свойство, а не недостаток. Не искать защиты в модульности и в лицензии: они про деньги и удобство, не про секреты.
Пункты 1–2 закрывают то, что находится за минуты. Пункт 3 — то, что нельзя закрыть технически. Остальное — по мере сил.
Ход работ (18.09, вечер)
Пункт 1 — вынос справки — СДЕЛАНО (коммит fcded5f)
Технические страницы /admin/help/* (архитектура НРД, протокол FTV_M2M на 41 КБ,
база, крипто, QUIK) вынесены за build-tag internaldocs. Клиентский релиз
(build-release.sh, обычный go build) их не содержит — заглушка «доступно на
портале»; наша сборка go build -tags internaldocs их включает. Проверено на
собранных бинарях: «Согласовано 10.09», «Для программиста Fancy», «как устроен
обмен» → 0 совпадений в клиентской сборке; −27 тыс. символов внутренней прозы.
Операторские страницы (ручной ввод, робот, системы, API ЛК) остались.
Пункт 2 — обфускация garble — ПРОВЕРЕНО, НЕ ВНЕДРЕНО (нужен ваш выбор)
Что garble даёт (проверено на реальном bj-server, сборка через go1.26.8):
- имена функций пакета
lkgatewayв бинаре: 72 → 0; - SQL-рецепт
OD_RESTS,spectreProceeds, путьinternal/lkgateway/lkflow.go: → 0; - строки
SELECT …: 128 → 11; литералы SQL шифруются и расшифровываются только в момент выполнения —stringsих больше не выдаёт; - бинарь запускается,
/healthzи/admin/отвечают 200.
Но: garble ломает наши HTML-страницы. html/template обращается к полям
структур по имени во время выполнения ({{$p.AsOf}}), а garble переименовывает
поля (AsOf → случайное) — шаблон падает: «can't evaluate field AsOf». Это
системно: так устроена каждая страница админки и кабинета. Опции «не трогать
имена полей» у garble нет; проблема известная (reflection в html/template
garble не видит, т.к. Execute принимает any).
Разблокировать garble можно, только передавая в шаблоны данные как
map[string]any вместо структур. Это крупная переделка всех обработчиков
страниц и постоянный налог на каждую новую страницу.
Решение — за вами:
- A. Остановиться на пункте 1 + договор (пункт 3). Самая дешёвая и крупная утечка (документация) закрыта; в коде остаются SQL-литералы и имена функций, но чтобы ими воспользоваться, всё равно нужно знание DEPO и НРД, а разбор — нарушение договора. Рекомендую как базовый вариант.
- B. Инвестировать в garble. Перевести данные шаблонов на map (недели работы,
риск регрессий в вёрстке), тогда
strings/декомпилятор перестают что-либо давать. Оправдано, если защита кода критична сама по себе.
Тулчейн go1.26.8 и garble установлены в ~/sdk и ~/go/bin — если выберете B,
среда готова.
Пункт 2 — РЕШЕНО через селективный garble (коммит 7ce4c2c)
Прямой garble ломает html/template, но GOGARBLE позволяет обфусцировать
только выбранный пакет. Весь чувствительный SQL (95 запросов к DEPO/SPECTRE/
QUIK и таблицам FTV_M2M) вынесен в internal/lkgateway/fsql как var-строки
(не const — иначе компилятор инлайнит их обратно в необфусцируемый код).
build-release.sh собирает bj-server c GOGARBLE=.../fsql garble -literals
(нужен SDK go1.26 в ~/sdk/go1.26.8 + garble в ~/go/bin; иначе обычная
сборка с предупреждением). Шаблоны/обработчики не затронуты.
Проверено на garble-бинаре: рецепт чтения чужих систем из бинаря исчез —
OD_RESTS, OD_WIRING, OD_TURNS, OD_BALANCES, D_B_CONTRACTS,
OD_CLIENTS_PORTS, DepoLimits, MoneyLimits → 0. Приложение работает.
Остаётся сделать при желании (тем же приёмом fsql-var): format-строки и логи с именами FTV_M2M (протокол Fancy — видно, что таблицы есть, но не рецепт), и вынести имена таблиц из операторских embed-шаблонов справки.