Проблема

Каждый промпт, отправленный в LLM-провайдер — OpenAI, Anthropic, Google — передаётся открытым текстом. Имена клиентов, email-адреса, ИНН, СНИЛС, номера паспортов оказываются в логах провайдера.

Если ваше приложение обрабатывает данные российских пользователей, это касается ИНН (идентификационный номер налогоплательщика), СНИЛС (страховой номер), номеров телефонов и паспортных данных.

CloakLLM

CloakLLM — это open-source middleware, которое перехватывает вызовы LLM API, обнаруживает персональные данные, заменяет их обратимыми токенами до отправки промпта провайдеру и восстанавливает оригиналы в ответе.

pip install cloakllm
from cloakllm import Shield, ShieldConfig

shield = Shield(ShieldConfig(locale="ru"))

text = "Иванов Иван Иванович, ИНН 7707083893, СНИЛС 112-233-445 95, телефон +7-912-345-6789"
sanitized, token_map = shield.sanitize(text)
# → "[PERSON_0], ИНН [INN_RU_0], СНИЛС [SNILS_RU_0], телефон [PHONE_RU_0]"

# Отправляем санитизированный промпт в LLM — провайдер не видит реальные данные
# ...

# Восстанавливаем оригиналы в ответе
restored = shield.desanitize(llm_response, token_map)

Детекция для России

v0.4.0 добавляет locale-aware детекцию для России:

Категория

Что обнаруживает

Пример

INN_RU

ИНН физлица/юрлица (10 или 12 цифр, с контекстной привязкой)

ИНН 7707083893

SNILS_RU

СНИЛС (формат XXX-XXX-XXX XX)

112-233-445 95

PHONE_RU

Российские номера телефонов (+7, 8-)

+7-912-345-6789, 8(495)123-45-67

PASSPORT_RU

Внутренний паспорт (формат XXYY NNNNNN)

4510 123456

Контекстная привязка: паттерн ИНН требует предшествующее ключевое слово (инн, ИНН, inn) для предотвращения ложных срабатываний на произвольных числовых последовательностях.

Настройка locale также автоматически выбирает модель spaCy ru_core_news_sm и предоставляет русскоязычные подсказки для опционального Ollama LLM-детектора.

Архитектура: 3-проходный pipeline детекции

CloakLLM использует три прохода детекции, каждый из которых дополняет предыдущий:

Проход 1: Regex — высокоточное сопоставление шаблонов для структурированных данных. Email, банковские карты, IBAN и locale-специфичные форматы (ИНН, СНИЛС и т.д.). Поддерживаются пользовательские паттерны:

shield = Shield(ShieldConfig(
    locale="ru",
    custom_patterns=[
        ("POLICY_NUMBER", r"POL-\d{8}"),
    ]
))

Проход 2: spaCy NER — распознавание именованных сущностей: имена (PERSON), организации (ORG), географические объекты (GPE). Модель выбирается автоматически по locale.

Проход 3: Ollama LLM (opt-in) — локальная LLM-детекция для контекстно-зависимых ПД: адреса, медицинские термины, даты рождения. Данные не покидают вашу машину.

shield = Shield(ShieldConfig(
    locale="ru",
    llm_detection=True,
    llm_model="llama3.2",
))

Токенизация

Обнаруженные сущности заменяются детерминированными токенами:

Иванов Иван Иванович → [PERSON_0]
7707083893           → [INN_RU_0]
112-233-445 95       → [SNILS_RU_0]
+7-912-345-6789      → [PHONE_RU_0]

Токены детерминированы в рамках сессии — одна и та же сущность всегда отображается в один и тот же токен. TokenMap хранит двунаправленное отображение и может переиспользоваться в многоходовых диалогах:

# Ход 1
sanitized1, token_map = shield.sanitize(prompt1)

# Ход 2 — те же сущности получают те же токены
sanitized2, token_map = shield.sanitize(prompt2, token_map=token_map)

Аудит-логирование

Каждая операция sanitize/desanitize записывается в hash-chained JSONL файлы:

{
  "seq": 1,
  "event_type": "sanitize",
  "entity_count": 4,
  "categories": {"PERSON": 1, "INN_RU": 1, "SNILS_RU": 1, "PHONE_RU": 1},
  "prompt_hash": "sha256:9f86d0...",
  "sanitized_hash": "sha256:a3f2b1...",
  "prev_hash": "sha256:000000...",
  "entry_hash": "sha256:b5e8f3..."
}

Персональные данные не сохраняются в логах — только хеши и счётчики токенов. Каждая запись связана с предыдущей через SHA-256, образуя цепочку с защитой от подделки. Изменение любой записи ломает все последующие хеши.

python -m cloakllm verify ./cloakllm_audit/
# Audit chain integrity verified — no tampering detected.

Криптографическая аттестация

v0.3.2 добавил Ed25519-подписанные сертификаты санитизации. Каждый вызов sanitize() создаёт подписанное доказательство, содержащее хеши входа/выхода, количество сущностей, категории и проходы детекции. Пакетные операции используют деревья Меркла.

from cloakllm import DeploymentKeyPair

keypair = DeploymentKeyPair.generate()
shield = Shield(ShieldConfig(locale="ru", attestation_key=keypair))

sanitized, token_map = shield.sanitize("Иванов, ИНН 7707083893")
cert = token_map.certificate
assert cert.verify(keypair.public_key)  # криптографическое доказательство

Кросс-языковая совместимость — подпишите в Python, верифицируйте в JavaScript.

Интеграция как middleware

Одна строка для защиты всех LLM вызовов:

# OpenAI SDK
from cloakllm import enable_openai
from openai import OpenAI

client = OpenAI()
enable_openai(client)

# Все вызовы автоматически санитизируются/десанитизируются
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Найди информацию по ИНН 7707083893"}]
)
# Провайдер не видел реальный ИНН
# LiteLLM — покрывает 100+ провайдеров
import cloakllm
cloakllm.enable()

JavaScript:

const cloakllm = require('cloakllm');
cloakllm.enable(client);  // OpenAI SDK
// Vercel AI SDK
const { createCloakLLMMiddleware } = require('cloakllm');
const middleware = createCloakLLMMiddleware();
const model = wrapLanguageModel({ model: openai('gpt-4o'), middleware });

Также доступен как MCP-сервер для Claude Desktop и Cursor.

Усиление безопасности (v0.4.0)

В этом релизе исправлено 33 уязвимости:

  • Защита от SSRF при обращении к Ollama URL (блокировка приватных IP-диапазонов)

  • Потокобезопасные token map и аудит-логирование

  • Ограничения на размер входных данных MCP (1MB текст, 100 элементов в батче)

  • Защита сертификатов от replay-атак (UUID4 nonce)

  • Редактированный CLI-вывод по умолчанию

  • Валидация путей для директорий логов

Поддерживаемые локали

Локаль

Паттерны

Russian (ru)

ИНН, СНИЛС, паспорт, телефон

English (en)

SSN, US phone

German (de)

Steuer-ID, Sozialversicherungsnummer, IBAN

French (fr)

NIR, carte d'identité, телефон

Spanish (es)

DNI/NIE, телефон

Portuguese-BR (pt-BR)

CPF, CNPJ, RG, телефон

Japanese (ja)

My Number, телефон, паспорт

Chinese (zh)

身份证号, телефон

Korean (ko)

주민등록번호, телефон

Polish (pl)

PESEL, NIP, REGON

Dutch (nl)

BSN, телефон

Hindi (hi)

Aadhaar, PAN, телефон

Hebrew (he)

Teudat Zehut, телефон

Цифры

  • 527 тестов (260 Python, 234 JS, 33 MCP)

  • 36 locale-специфичных паттернов

  • 13 локалей

  • ~2,300 загрузок/неделю

  • MIT лицензия

  • Ноль runtime-зависимостей в JS SDK

Ссылки