Маскирование и потоковая защита
Обнаруженные чувствительные участки должны редактироваться на месте, в потоке байт в реальном времени, без буферизации всего вывода команды и без нарушения структуры окружающих данных (кадры JSON-RPC, терминальные управляющие последовательности, частичные строки). В этой главе описывается механика этого процесса.
Проблема потоковой обработки
Наивный подход — накопить всё в буфере, проверить один раз и вывести — сводит на нет смысл терминала или транспорта MCP: вывод должен появляться по мере его формирования. Но секрет может оказаться разделён границей между двумя вызовами записи (половина ключа API в одном фрагменте, половина — в следующем), поэтому механизм не может просто проверять и выводить каждый фрагмент независимо.
%%{init: {"theme": "base", "themeVariables": {"fontFamily": "-apple-system, Segoe UI, Helvetica, Arial, sans-serif", "primaryColor": "#eef2f7", "primaryTextColor": "#0b0b0b", "primaryBorderColor": "#5b6b7c", "lineColor": "#5b6b7c", "secondaryColor": "#eef4ff", "tertiaryColor": "#f4f3f0", "clusterBkg": "#f9f9f7", "clusterBorder": "#c3c2b7", "edgeLabelBackground": "#fcfcfb", "actorBkg": "#eef2f7", "actorBorder": "#5b6b7c", "actorTextColor": "#0b0b0b", "actorLineColor": "#5b6b7c", "signalColor": "#52514e", "signalTextColor": "#0b0b0b", "labelBoxBkgColor": "#eef4ff", "labelBoxBorderColor": "#5b6b7c", "labelTextColor": "#0b0b0b", "loopTextColor": "#0b0b0b", "noteBkgColor": "#fdf3d9", "noteBorderColor": "#c98500", "noteTextColor": "#3d2f00", "activationBorderColor": "#5b6b7c", "activationBkgColor": "#eef4ff", "sequenceNumberColor": "#ffffff"}}}%%
flowchart LR
In["Входящие байты\n(вывод команды / JSON-RPC)"] --> Pending["Буфер ожидания\n(макс. 64 КБ)"]
Pending --> Scan["Поиск совпадений,\nотсортированных по смещению"]
Scan --> Straddle{"Совпадение пересекает\nграницу записи?"}
Straddle -- да --> Retain["Удержать хвост\n(≥ длины самого длинного\nвозможного совпадения,\nпо умолчанию 16 КБ)"]
Straddle -- нет --> Emit["Безопасно вывести\nдо безопасной границы"]
Retain --> Idle{"Простой 20 мс\nбез новых данных?"}
Idle -- да --> ForceEmit["Принудительный сброс\n(не удерживать короткие значения,\nтакие как интерактивные приглашения)"]
Idle -- нет --> Pending
Механика и константы
- Замена на основе смещений, один проход, совпадения отсортированы по позиции; если совпадения перекрываются, побеждает более длинное совпадение. Пустой пустая замещающая строка оставляет соответствующую область без изменений.
- Удерживаемый хвост: байты, удерживаемые на границе безопасного вывода, размером не менее длины самого длинного возможного совпадения, которое может дать активный набор механизмов обнаружения (по умолчанию 16 КБ) — именно это предотвращает проскальзывание наполовину замаскированного секрета, разделённого между двумя записями.
- Максимальный буфер ожидания: 64 КБ. При нехватке памяти буфер обрезается по концу пересекающего границу совпадения — никогда не посередине значения — поэтому ограничение потребления памяти никогда не приводит к утечке данных; в худшем случае оно приводит лишь к более раннему принудительному сбросу.
- Сброс по простою: 20 мс. Без этого короткое значение (интерактивное приглашение, эхо оболочки), которое самостоятельно никогда не достигает порога размера хвоста, удерживалось бы бесконечно. Это осознанный компромисс в пользу отзывчивости терминала.
- Сброс полностью опустошает буфер — после финального/явного сброса удерживаемого хвоста не остаётся.
- Замещающие строки подставляются на месте — замаскированный блок PEM отображается как
-----[MASKED:PRIVATE_KEY]-----, сохраняя окружающую структуру.
Производительность
- Тест производительности с синтетическим механизмом обнаружения (
BenchmarkStreamingWriter100MB, воспроизводимый тест Go) фиксирует пропускную способность около 1,5 ГБ/с для самого механизма маскирования на том оборудовании, на котором он выполнялся.