Системный журнал — практичная база знаний по VPS и Linux
security • 12 минут

Как построить собственный harness для поиска уязвимостей в коде на Linux/VPS

Современные кодовые базы становятся всё более распределёнными и polyglot‑ориентированными. Чтобы эффективно выявлять уязвимости в таких средах, нужна система, которая не привязана к конкретной языковой парсере или отдельной модели ИИ, а может работать как interchangeable компонент. В этой статье мы опишем, как собрать такой harness на обычном Linux‑сервере или VPS, используя Docker, SQLite и простые скрипты оркестрации.

Основные идеи harness‑подхода

Цель harness‑а — обеспечить непрерывный, масштабируемый процесс поиска уязвимостей, при котором:

  • Модели ИИ рассматриваются как Stateless вычислительные движки; всё состояние хранится вне их контекста.
  • Каждый этап пайплайна (разведка, охота, валидация) выполняется отдельным агентом, получающим задачу из очереди.
  • Результаты сохраняются в базе данных, что позволяет возобновлять работу после сбоя и избегать дублирования.
  • Для снижения ложных срабатываний используется adversarial review: одна модель генерирует гипотезы, другая — пытается их опровергнуть.

Такой подход делает систему устойчивой к изменениям в базовых моделях: вы можете подменять их без изменения самой оркестрации.

Архитектура пайплайна

Harness состоит из двух основных подсистем:

  1. VDH (Vulnerability Discovery Harness) — отвечает за первичное обнаружение потенциальных проблем в коде.
  2. VVS (Vulnerability Validation System) — получает Findings из VDH и проводит deduplication, оценку и подготовку к исправлению.

Каждая подсистема реализована как набор этапов, связанных через очередь задач. Этапы работают в режиме producer‑consumer, записывая промежуточные результаты в одну SQLite‑базу, ключевую по кортежу (run_id, repo, stage). Это обеспечивает:

  • Persistency — при падении контейнера достаточно перезапустить orchestrator, и работа продолжится с того же места.
  • Возможность параллельного выполнения без потери контекста, так как каждый агент получает только свою порцию данных.
  • Лёгкую отладку: можно посмотреть, какие задачи были обработаны на каждом этапе.
  • Этап обнаружения (VDH)

    VDH реализует многократный цикл, в котором:

    • Recon — агент строит угрозовую модель конкретного репозитория, выделяя классы атак (инъекции, повреждение памяти, race conditions и т.д.) и, при необходимости, генерирует repo‑специфичные правила.
    • Hunt — несколько агентов параллельно пытаются «сломать» код, используя sandbox‑окружение. Они компилируют фрагменты, запускают их и фиксируют аварийные ситуации.
    • Gapfill / Feedback / Trace — генерируют новые задачи на основе промежуточных результатов (например, выявляют недостающие зависимости или переходят к связанным репозиториям).
    • Dedup — на этом этапе агрегируются похожие находки, чтобы уменьшить шум перед передачей в VVS.
    • Validate — отдельный агент, которому запрещено создавать собственные Findings, пытается опровергнуть каждую гипотезу, используя только детерминированные проверки (существование файлов, корректность патча и теста).

    Важно ограничивать размер контекста каждой модели: мы держим её ниже 25 % от общего окна, иначе модель начинает «забывать» ранее найденные проблемы и генерировать ложные срабатывания.

    Этап валидации (VVS)

    Все Findings, прошедшие VDH, попадают в общую очередь VVS, где они проходят:

    1. Deduplication — детерминированный код строит инвертированные индексы по touched files, функциям и редким токенам, получая короткий список кандидатов. Только затем LLM анализирует этот список, решая, можно ли объединить несколько находок в одну.
    2. Judgment — другая модель оценивает достоверность каждой находки, проверяя наличие рабочего PoC и предлагаемого патча.
    3. Fixing — после подтверждения формируется задача на исправление, которая может быть передана в систему управления задачами или в CI/CD.

    Разделение моделей между VDH и VVS гарантирует, что каждая находка оценивается независимым набором весов весов, что существенно снижает вероятность системных ошибок.

    Управление состоянием и persistence

    Все этапы записывают свои результаты в одну SQLite‑таблицу:

    CREATE TABLE IF NOT EXISTS task_results (
        run_id TEXT NOT NULL,
        repo   TEXT NOT NULL,
        stage  TEXT NOT NULL,
        payload BLOB,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (run_id, repo, stage)
    );
    

    Оркестратор просто читает из этой таблицы, какие задачи ещё не выполнены, и подаёт их в соответствующие агенты. При ошибке API или падении контейнера достаточно перезапустить процесс — уже выполненные этапы не будут повторяться.

    Динамическое моделирование угроз и микро‑форки

    На этапе Recon агент может самостоятельно создавать новые классы атак, специфичные для исследуемого кода. Это позволяет адаптироваться к нестандартным уязвимостям, например, к timing side‑channels в криптографических библиотеках.

    Если Hunter обнаруживает интересный путь, выходящий за текущий scope, он использует механизм Sibling Forking: создаёт копию самого себя с точным структурным seed‑ом, чтобы исследование продолжилось без потери контекста основного анализа.

    Когда агенту нужен внешний ресурс (например, специальная сборочная среда или VM), он записывает запрос в общий Wishlist. Система периодически проверяет этот список и, после предоставления зависимости человеком, автоматически перезапускает связанную задачу.

    Трассировка зависимостей между репозиториями

    Для выявления сквозных уязвимостей harness строит общий символ‑индекс и граф зависимостей всех репозиториев в fleet. Tracer‑агент проверяет, может ли вредоносный input достичь уязвимого места через цепочку вызовов. Если да, он порождает новые hunt‑задачи уже в потребительском репозитории.

    Этот этап требует:

    • Доступ к исходному коду всех репозиториев (через монтированные тома или git‑clones внутри контейнеров).
    • Скрипт, который собирает export‑symbols (например, через nm, readelf или язык‑специфичные инструменты) и сохраняет их в той же SQLite‑базе.

    Борьба с ложными срабатываниями

    Чтобы исключить «vacuous findings» (например, «если пользователь может писать в БД, он может писать в БД»), harness требует от Hunter:

    1. Явно сформулировать threat model: кто атакующий, какой границы нарушается.
    2. Предоставить рабочий PoC в виде теста, который выполняется против неизменённого кодобазы.
    3. Предложить патч, проходящий базовую проверку (применяется к чистому кодобазу и не ломает сборку).
    4. Пройти детерминированную валидацию, которая подтверждает existence of cited files и корректность patch‑а и теста.

    Если любой из пунктов не выполнен, находка помечается как непригодная и не попадает в очередь VVS.

    Практические советы по развертыванию на VPS

    1. Окружение — используйте свежий дистрибутив Ubuntu 22.04 или Debian 12. Установите Docker и docker‑compose.

    2. Создание сети — изолируйте harness в отдельной Docker‑сети, чтобы ограничить доступ к внешним сервисам.

    3. Запуск sandbox‑агента — если ваш Hunter работает внутри Docker, добавьте при запуске:

    docker run --rm \
        --security-opt seccomp=unconfined \
        --security-opt apparmor=unconfined \
        -v /path/to/repo:/src:ro \
        my-hunter-image
    

    Эти опции необходимы, иначе вызов unshare внутри контейнера будет заблокирован ядром.

    4. База данных — разместите SQLite‑файл в томе, монтируемом в контейнеры, чтобы все этапы видели одно и то же состояние.

    5. Оркестрация — простейший оркестратор можно написать на Python: цикл, который выбирает из базы задачи со статусом «pending», запускает соответствующий образ Docker с нужными переменными окружения и обновляет запись после завершения.

    6. Мониторинг — экспортируйте метрики количества выполненных задач, среднее время этапа и количество уникальных Findings в Prometheus, используя готовый экспортер или простой HTTP‑эндпоинт.

    7. Резервное копирование — периодически делайте snapshot томов с кодом и базой данных; это позволит быстро восстановить работу после сбоя узла.

    Заключение

    Построив модель‑независимый harness, вы получаете систему, которая:

    • Не привязана к конкретной версии или поставщику LLM.
    • Масштабируется на сотни репозиториев благодаря внешнему хранению состояния и графу зависимостей.
    • Снижает ложные срабатывания за счёт adversarial review и обязательного PoC.
    • Легко развёртывается на обычном VPS с Docker и минимальным набором скриптов.

    Такой подход позволяет сосредоточиться на улучшении промптов и логики охоты, а не на постоянной переделке инфраструктуры при смене базовой модели. Если вы уже экспериментируете с одиночными сканерами на одном репозитории, следующим логичным шагом будет вынести каждый этап в отдельный контейнер, добавить persistence‑слой и получить готовый к промышленному использованию harness для непрерывного поиска уязвимостей.