Разбор практического кейса по работе с разными слоями безопасности на примере платформы для создания ИИ-агентов
Разбор практического кейса по работе с разными слоями безопасности на примере платформы для создания ИИ-агентов

Введение

Всем привет. В сегодняшней статье речь пойдет о личном опыте построения архитектуры безопасности на примерах построения собственной платформы. Мы разберем гибридные и классические подходы, посмотрим на реальные примеры, проблемы и решения.

Когда мы начинали строить платформу ТожеБот, у нас была не совсем обычная задача. Мы не просто хотели сделать «платформу для ИИ-агентов». Мы хотели построить систему, в которой классическая безопасность, изоляция контуров и защита от угроз нового поколения работают не как отдельные компоненты, а как единая ткань. Как технический директор, я видел слишком много проектов, где шифрование есть, а данные всё равно утекают. Где мультитенантность заявлена, но изоляция - это просто фильтр в SQL-запросе. Где guardrails добавлены «сверху» как пластырь, а не встроены в архитектуру.

Мы пошли другим путём. Каждый слой безопасности платформы спроектирован с учётом того, что другие слои существуют. Ключи ротируются не просто по расписанию, а с оглядкой на активные сессии и незавершённые миграции. Шифрование полей не ломает валидацию данных и поиск по базам знаний. Guardrails не блокируют легитимные запросы, но детектируют галлюцинации LLM и другие угрозы. И всё это работает в мульти-тенантной среде, где компромисс одного тенанта не должен затрагивать других.

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

Итак, начнем путешествие!


Классическая безопасность с оглядкой на будущее и управление ключами

Пост-квантовые и классические алгоритмы шифрования
Пост-квантовые и классические алгоритмы шифрования

Выбор технологии

Когда мы выбирали криптографический фундамент, у нас было два пути: использовать проверенные стандарты (например, AES-256-GCM) или пойти на риск и внедрить гибридные решения с пост-квантовой устойчивостью. Классический подход выглядит безопасным - до тех пор, пока не задумаешься о горизонте планирования. Данные, зашифрованные сегодня, могут оставаться конфиденциальными десятилетиями. Атака «храни сейчас, расшифруешь позже» - это не теоретическая угроза: злоумышленник может перехватить шифротекст сегодня и расшифровать его, когда появится достаточно мощный квантовый компьютер. А с сегодняшними успехами в этом направлении, есть над чем задуматься.

Ранее я уже работал над гибридными алгоритмами, в том числе реализуя собственный протокол QuarkDash, успешно показывающий хорошие результаты для чатов. Он решает эту проблему, сочетая пост-квантовую стойкость Ring-LWE с производительностью симметричных шифров ChaCha20 и Gimli. Бенчмарки показывали пропускную способность до 2,8 ГБ/с и установление сессии менее чем за 10 мс на обычном железе даже в TypeScript реализации, что хорошо ложиться в рамках платформы. Для нас это было критично: мы не могли пожертвовать скоростью ради безопасности.

Так же существует порт для Go - который работает в целом ещё быстрее за счет особенностей языка

Но дело не только в производительности. Архитектура QuarkDash позволяет выводить ключи не только в классическом обмене (как это делается в чатах), но и детерминированно: из мастер-секрета, соли и версии. Это означает, что нам не нужно хранить сами ключи в базе данных. Только метаданные: соль, версию, KDF-параметры. Компрометация долгосрочного ключа не раскрывает прошлые сессии - свойство, которое в криптографии называется perfect forward secrecy.

В нашем KeyManager это реализовано через функцию deriveKeys, которая добавляет к соли четыре байта с номером версии:

// keyManager.ts: Вывод ключей с версионной солью
// Показан пример того, как это примерно работает на платформе, 
// без лишних деталей реализации
private async deriveKeys(
  version: number,
  kdf: KdfParams,
): Promise<{ sessionKey: Buffer; macKey: Buffer }> {
  const versionBytes = Buffer.alloc(4)
  versionBytes.writeUInt32BE(Number(version), 0)
  const versionedSalt = new Uint8Array(Buffer.concat([this.salt, versionBytes]))

  const { sessionKey, macKey } = await QuarkDashPassphrase.deriveKeyForQuarkDash(
    this.options.masterSecret,
    versionedSalt,
    opts,
  )

  return {
    sessionKey: Buffer.from(sessionKey),
    macKey: Buffer.from(macKey),
  }
}

И тут мы приходим к вызову, связанному с ротацией ключей.

Ротация ключей: когда безопасность встречается с производительностью

Ротация ключей - это классическое требование комплаенса. Но реализовать её в мультитенантной среде с миллионами документов - это отдельная головная боль. Если ротировать ключ каждые 24 часа и при этом перешифровывать все данные сразу, мы получим простой. Если не перешифровывать - старые данные останутся на уязвимом ключе.

Мы решили эту проблему через ленивую миграцию и фоновое ре-шифрование. Ключ ротируется по расписанию, но данные перешифровываются постепенно, пакетами по 100 документов с задержкой между батчами. Процесс можно приостановить и возобновить - прогресс сохраняется в keyring-документе.

Самое интересное здесь - это взаимодействие с основным ядром платформы. По-умолчанию наш API автоматически расшифровывает поля в afterRead-хуке. Это значит, что при обычном чтении мы видим plaintext, а не шифротекст. Но для ре-шифрования нам нужен именно шифротекст - чтобы определить версию ключа и понять, требует ли документ миграции. Мы решили это через контекст запроса:

// keyManager.ts: Получение сырого шифротекста для ре-шифрования
// На самом деле там контекст чуть шире, но для понимания показываем
// только самое важное
const migrationReq = {
  user: undefined,
  context: { skipDecryption: true },
} as any

const result = await db.find({
  collection,
  limit: batchSize,
  where,
  sort: 'id',
  overrideAccess: true,
  depth: 0,
  req: migrationReq,
})

Когда skipDecryption установлен, decryptHook возвращает значение без изменений - и мы видим ровно то, что лежит в базе. А при обновлении документа контекст уже не передаётся, поэтому encryptHook срабатывает штатно и перешифровывает plaintext активным ключом.

Но это ещё не вся головная боль.

Защита от drift: почему rotation lock - это не паранойя

В мультитенантной среде легко получить ситуацию, когда версии ключей начинают накапливаться быстрее, чем данные мигрируют. Например, если каждый новый сеанс пользователя запускает ротацию. Через неделю у нас будет 50 версий ключей, 49 из которых содержат данные, требующие миграции.

Для решения этой проблемы, мы встроили в KeyManager три уровня защиты:

  • Первый - cooldown в 5 минут для session-bound ротации.

  • Второй - проверка на наличие незавершённых миграций: если есть фоновые задачи в ожидании, ротация блокируется.

  • Третий - лимит на количество версий (KEY_MAX_VERSION_DRIFT). Если версий больше 50, система переходит в режим read-only для ротации - нужно сначала завершить миграцию.

// keyManager.ts: Проверка блокировок ротации
// Пример так же упрощенный, чтобы показать саму суть
// реализации локинга ротаций
private checkRotationLock(): RotationLockCheck {
  if (this.explicitLock.locked) {
    return { locked: true, reason: this.explicitLock.reason ?? 'Explicitly locked' }
  }

  /* Pending migrations block rotation */
  const pending = this.getPendingJobs()
  if (pending.length > 0) {
    return {
      locked: true,
      reason: 'Pending re-encryption jobs in progress',
      details: { source: 'pending-jobs', jobs: pending.map(...) },
    }
  }

  /* Version drift - too many versions pile up = something is wrong */
  if (this.versionRecords.length > this.options.maxVersionDrift) {
    return {
      locked: true,
      reason: `Version drift exceeds KEY_MAX_VERSION_DRIFT (${this.options.maxVersionDrift})`,
    }
  }

  return { locked: false }
}

Это не просто техническая предосторожность. Это требование комплаенса. Регуляторы хотят видеть, что система способна объяснить, почему ключ не был ротирован, и предоставить аудиторский след. Наш checkRotationLock возвращает детальную информацию о причине блокировки, которая логируется и доступна через админ-эндпоинты.

На этом закончим с шифрованием и перейдем к другому классическому слою безопасности.


Изоляция контуров и архитектура Zero Trust

Разбираемся с Zero-trust, что же это такое на самом деле
Разбираемся с Zero-trust, что же это такое на самом деле

Мультитенантность: почему фильтр в SQL - это не изоляция

Самая распространённая ошибка в мульти-тенантных системах - полагаться на то, что разработчик не забудет добавить WHERE tenant_id = ? в каждый запрос. Я сам видел это в десятках проектов: один пропущенный фильтр - и данные одного клиента утекают к другому.

Наш подход - контекстная изоляция. Каждая операция с базой данных выполняется в контексте тенанта, и этот контекст передаётся насквозь: от HTTP-запроса до вызова инструмента в конечных ИИ-агентах. В agentRunner.ts мы явно извлекаем tenantId из диалога и передаём его в контекст выполнения инструментов:

// agentRunner.ts: Извлечение tenantId и создание контекста
// Инструмент физически не получает данных вне этого тенанта, даже если сам агент
// забудет его подставить
const tenantId = extractId(conversation?.tenant)

const toolCtx: ToolExecutionContext = {
  api,
  req,
  agentId: agent.id,
  conversationId: conversation.id,
  tenantId: tenantId!,
  userId: conversation.user,
  mcpServers,
  agentTools,
}

Каждый инструмент внутри платформы, будь то create_lead, escalate_to_human или кастомный webhook - получает этот контекст. Это означает, что даже если инструмент попытается обратиться к коллекции без явного фильтра, система может применить изоляцию на уровне исполнителя. Это не просто защита в глубину - это принцип минимальных привилегий по-умолчанию.

Почему мы не используем общий пул ключей для всех тенантов

Немного вернувшись к вопросу о шифровании. Ещё один архитектурный выбор: у каждого тенанта - свой набор ключей шифрования. Это дороже с точки зрения управления (нужно хранить мета-данные для каждого тенанта), но это критично для комплаенса. Если один тенант требует удаления данных (GDPR или 152-ФЗ), мы можем просто уничтожить его ключи - и данные станут не читаемыми. Если бы ключи были общими, нам пришлось бы перешифровывать всю базу.

Этот подход также снижает радиус поражения при компрометации. Если злоумышленник получит доступ к keyring-документу одного тенанта, он не сможет расшифровать данные других.

Естественно, это далеко не всё, что связанно с классической безопасностью. Там же и защита от CSRF, и XSS, и SQL и другие классические виды атак, но сегодня мы их не рассматриваем, поскольку это стало уже базой.

На такой ноте, перейдем к более современным видам угроз.


Безопасность ИИ-агентов: где классические подходы ломаются

Новые технологии - всегда новые угрозы. И нельзя слепо доверяться базовым механизмам, заложенным в LLM, поскольку их природа подразумевает ненадежность таких механизмов.
Новые технологии - всегда новые угрозы. И нельзя слепо доверяться базовым механизмам, заложенным в LLM, поскольку их природа подразумевает ненадежность таких механизмов.

Промпт-инъекции: почему регулярки - это только начало

Первое, что приходит в голову при защите от промпт-инъекций - это регулярные выражения. «Ignore all previous instructions» - легко задетектить. «Забудь всё» - тоже. Но современные атаки давно ушли от этих очевидных формулировок. OWASP LLM Top 10 явно указывает, что защита должна быть многоуровневой: от изоляции недоверенного контента до семантической фильтрации.

Помимо изоляции по тенантам, мы построили систему из трёх фаз, которые дополняют друг друга. Первая фаза - эвристики на основе регулярных выражений. Они работают мгновенно (0,1 мс) и ловят примерно 30-40% атак. Это не просто фильтр - это первая линия обороны, которая не требует загрузки ML-модели и работает даже в условиях ограниченных ресурсов.

// guard.ts: Паттерны для детекции инъекций
// Первый и самый базовый паттерн для обнаружения угроз
const INJECTION_PATTERNS: Array<{ re: RegExp; score: number; label: string }> = [
  {
    re: /ignore\s+(all\s+)?(previous|above|prior)\s+instructions?/i,
    score: 0.95,
    label: 'ignore_previous',
  },
  { re: /jailbreak|dan\s+mode|developer\s+mode/i, score: 0.95, label: 'jailbreak' },
  {
    re: /(покажи|выведи|повтори|скажи)\s+(свой\s+)?(системный\s+)?(промпт|инструкции)/i,
    score: 0.95,
    label: 'leak_prompt_ru',
  },
  // ... 100+ паттернов
]

Следующий этап - это обученные ML-модели. Ранее я уже экспериментировал, создав библиотеку OGL-Mini, поэтому мы взяли её для основы будущего щита.

OGL-Mini: почему ONNX, а не внешний API

Вторая фаза - это до-обученная мной ONNX-модель OGL-Mini. Она была обученна на TF-IDF со 100 000 признаков и логистической регрессией. Задержка на слабом железе в 3-7 мс. Так зачем я выбрали локальную модель, а не внешний API типа OpenAI Moderation?

Ответ - изоляция контуров. Если каждый запрос пользователя отправляется на внешний API для проверки, мы нарушаем принцип «данные не покидают контур». Кроме того, внешний API добавляет 100-300 мс задержки за счет сетевого запроса. Для чат-бота это критично. OGL-Mini работает на CPU с задержкой менее 10 мс - это в 10-30 раз быстрее, чем бежать к внешнему API.

Ну и самое главное - это безусловный контроль. Мы можем дообучать модель на наших данных, которые увидели в мониторинге. Мы можем добавлять новые классы атак, не дожидаясь, пока внешний провайдер обновит свою модель. И мы можем гарантировать, что модель доступна - даже если внешний API недоступен.

Ленивая загрузка: почему мы не блокируем старт приложения

Один из самых сложных архитектурных вопросов - когда загружать ML-модель. Если загружать при старте, сервис будет недоступен несколько секунд. Если не загружать вообще, мы теряем вторую фазу защиты.

Мы выбрали ленивую загрузку. При первом запросе модель загружается в фоне, а система продолжает работать с регулярными выражениями. Как только модель готова - она включается в процесс. Это обеспечивает отказоустойчивость: даже если модель не загрузилась, базовая защита продолжает работать.

// guard.ts: Ленивая загрузка OGL-Mini
// Базовая проверка. 
// Модель должна быть доступна в общем контуре без изоляции от других модулей
function ensureGuardLoaded(): void {
  const status = getStatus()
  if (status.state === 'ready' || status.state === 'loading') return
  if (globalThis.__tozhebot_ogl_loading) return

  globalThis.__tozhebot_ogl_loading = (async () => {
    try {
      setStatus({ state: 'loading' })
      const mod: any = await import('hybrid-ai-guard').catch(() => null)
      // ... инициализация
    } catch (err) {
      setStatus({ state: 'failed', error: (err as Error).message })
    }
  })()
}

Маскировка PII: почему токены, а не удаление

Третья фаза - это защита от утечки персональных данных. Мы не просто удаляем PII - мы заменяем его на токены. Это важно, потому что модель теряет контекст, если из диалога вырезать все имена и телефоны. Представьте: клиент пишет «Меня зовут Elijah, мой телефон +7 999 123-45-67». Если мы удалим эти данные, модель не сможет создать лид.

Мы используем стабильные токены: одно и то же имя всегда заменяется на один и тот же токен в рамках сессии. Это сохраняет контекст для модели, но защищает данные. После получения ответа от LLM мы восстанавливаем оригинальные значения:

// piiMasker.ts: Маскировка PII с сохранением токенов
export function maskPii(
  text: string,
  pii: { hasPII: boolean; entities?: PIIEntity[] },
): MaskResult {
  // Прроверяем отдала ли модель нам персональные данные
  if (!pii.hasPII || !pii.entities?.length) {
    return { sanitized: text, tokenMap: new Map() }
  }

  const tokenMap = new Map<string, string>()
  const valueToToken = new Map<string, string>()
  const counters: Record<string, number> = {}

  const sorted = [...pii.entities].sort((a, b) => b.start - a.start)

  let out = text
  for (const ent of sorted) {
    const key = `${ent.type}:${ent.value}`
    let token = valueToToken.get(key)
    if (!token) {
      counters[ent.type] = (counters[ent.type] ?? 0) + 1
      token = `[${ent.type}_${counters[ent.type]}]`
      valueToToken.set(key, token)
    }
    tokenMap.set(token, ent.value)
    out = out.slice(0, ent.start) + token + out.slice(ent.end)
  }

  return { sanitized: out, tokenMap }
}

В диалогах всё это хранится в зашифрованном виде. Так что даже при утечке базы данных - злоумышленник увидит лишь что-то похожее:

v5:3JIjNcvJf0XIqLKT348/eP83VZBD53U00bAsPnHryHx/wPaneeBWXNvv8ZbpaWY3KJfKjseVyyMN7Q==

Детекция галлюцинаций: почему LLM врёт и что с этим делать

Самая коварная проблема LLM - это галлюцинации. Особенно видно это на более младших версиях модели (например, GPT-6 Luna или GPT-5 Mini). Модель может «подумать», что вызвала инструмент, и написать в тексте ответа что-то вроде: «Я соединяю вас с оператором». Но на самом деле инструмент не был вызван. Пользователь ждёт оператора, а его никто не подключает.

Мы решаем это двумя способами. Первый - детекция JSON-подобных tool calls в тексте. Если модель написала {"name": "escalate_to_human"}, мы это замечаем и выполняем вызов вручную:

// agentRunner.ts: Fallback на JSON-подобный tool call
const fake = detectFakeToolCall(assistantText, availableToolNames)

if (fake) {
  yield { type: 'tool_call', toolName: fake.toolName }

  let toolResult = ''
  try {
    toolResult = await executeTool(fake.toolName, fake.args, toolCtx)
  } catch (err) {
    toolResult = `Tool execution error: ${(err as Error).message}`
  }

  yield { type: 'tool_result', toolName: fake.toolName, text: toolResult }
  // ...
}

Второй - детекция обещаний эскалации без вызова инструмента. Если модель пишет «Соединяю вас с оператором», но не вызвала escalate_to_human, мы принудительно выполняем эскалацию. Это гарантирует, что пользователь не останется в подвешенном состоянии.

Теперь, пройдясь по базовому слою защиты, перейдем к работе с комплайенсом.


Комплаенс как архитектурное требование, а не бюрократия

NIST AI RMF: Govern, Map, Measure, Manage

Фреймворк менеджмента рисков при использовании ИИ от NIST
Фреймворк менеджмента рисков при использовании ИИ от NIST

Когда мы проектировали систему, мы держали в голове четыре функции NIST AI Risk Management Framework: Govern, Map, Measure, Manage. Для нас это не просто чеклист, а операционная рутина.

  • Govern: это наш KeyManager и обвязанный вокруг шифрования функционал. Мы можем в любой момент сказать: «Ротация заблокирована, потому что идёт миграция».

  • Map: это EncryptedFieldsRegistry, который автоматически собирает список зашифрованных полей из конфигурации наших моделей баз данных. Нам не нужно вести отдельный реестр - он генерируется из кода.

  • Measure это метрики: сколько документов мигрировано, сколько версий ключей активно, сколько атак на агенты заблокировано. Всё это логируется и доступно через админ-эндпоинты.

  • Manage это автоматические действия: ротация, ре-шифрование, эскалация при обнаружении инъекций.

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

EU AI Act: почему мы готовимся заранее

Статья 15 EU AI Act требует, чтобы высокорисковые ИИ-системы были «точными, надёжными и кибербезопасными», а также сопротивлялись попыткам несанкционированного изменения. Наши guardrails - это именно та защита, которая предотвращает манипуляцию поведением агента. А логирование всех событий безопасности в коллекцию security-events - это основа для отчётности, требуемой статьёй 73.

Аудит: почему логирование - это не просто «хорошая практика»

В мультитенантной среде аудит - это обязательство. Мы не можем просто сказать клиенту: «Мы вас защищаем». Мы должны доказать, что защита работает. Наш logSecurityEvent фиксирует не только тип события, но и контекст: какой тенант, какой пользователь, какое поле, какая вероятность атаки.

// audit.ts: Логирование событий безопасности
// Пример базовой функции логирования
export async function logSecurityEvent(args: {
  api: Api
  req: ApiRequest
  tenantId?: string | number
  type: string
  severity: string
  input?: string
  reason?: string
  attackProbability?: number
  metadata?: Record<string, unknown>
}): Promise<void> {
  try {
    await args.api.create({
      collection: 'security-events',
      data: {
        type: args.type as any,
        severity: args.severity as any,
        tenant: args.tenantId as any,
        input: args.input,
        reason: args.reason,
        attackProbability: args.attackProbability,
        metadata: args.metadata,
      },
      overrideAccess: true,
      req: args.req,
    })
  } catch (err) {
    args.api.logger.warn({ err }, 'Failed to log security event')
  }
}

Теперь, давайте перейдем к подведению итогов.


Заключение: безопасность как единая ткань

Если вы дочитали до этого места, вы, наверное, заметили закономерность. Каждый компонент нашей системы безопасности существует не сам по себе. KeyManager знает о фоновых задачах, потому что ротация ключей может сломать миграцию. encryptedField знает о skipDecryption, потому что ре-шифрование требует сырого шифро-текста. guard.ts знает о PII-маскировке, потому что модель не должна видеть персональные данные. agentRunner знает о guardrails, потому что эскалация должна срабатывать автоматически. И так далее.

Когда мы говорим о принципах безопасности, речь не о списке абстрактных ценностей. Речь о конкретных решениях, которые иногда кажутся избыточными - до того момента, когда они спасают систему. Мы придерживаемся нескольких правил, которые пронизывают каждый наш архитектурный выбор.

Мы строили систему, в которой безопасность - это не пластырь, наклеенный сверху. Это архитектурный принцип, который пронизывает каждый слой: от криптографии до оркестрации агентов. И именно эта связность делает систему устойчивой к угрозам, которые мы пока не можем предсказать.

Безопасность - это не состояние, а процесс. Угрозы эволюционируют, и мы проектируем систему с расчётом на то, что завтра появится то, чего сегодня нет.

Квантовые компьютеры. Атака «храни сейчас, ломай потом» уже сегодня реальна: злоумышленник может сохранить наш шифротекст и ждать, пока квантовый компьютер станет достаточно мощным. QuarkDash с Ring-LWE делает эту стратегию бессмысленной - данные, зашифрованные сегодня, останутся защищёнными и после появления квантовых машин. Мы не ждём, пока NIST стандартизирует пост-квантовые алгоритмы полностью - мы уже используем гибридную схему, которая сочетает классическую и пост-квантовую стойкость.

Автоматизированные атаки на LLM. Сегодня промпт-инъекции пишут люди. Завтра их будет генерировать другой LLM - тысячи вариантов в секунду, каждый с уникальной обфускацией. Наши guardrails работают на уровне семантики, а не только паттернов: OGL-Mini обучен на обфусцированных примерах, включая base64, гомоглифы и zero-width символы. Регулярные выражения дополняют модель, но не заменяют её. Мы готовы к тому, что завтра атаки станут адаптивными - и наша архитектура позволяет до-обучать модель на новых примерах без пересборки всей системы.

Отравление RAG-базы знаний. Если злоумышленник получит доступ к базе знаний, он может внедрить в неё инструкции, которые LLM воспримет как руководство к действию. Это называется indirect prompt injection, и OWASP LLM Top 10 выделяет это как отдельную угрозу (LLM01). Мы уже сегодня проверяем контент из RAG на паттерны инъекций перед включением в контекст - и планируем расширить эту проверку на этапе индексации. Данные, попадающие в RAG, проходят через тот же inspectInput, что и пользовательский ввод.

Supply chain атаки на ML-модели. ONNX-модели - это бинарные файлы, и они могут быть подменены. Наш modelDownloader реализует принцип Trust-On-First-Use: хеш модели сохраняется при первой загрузке и проверяется при каждом запуске. Если хеш не совпадает - модель перезагружается. Мы не доверяем даже собственным моделям, скачанным с CDN, мы проверяем их целостность при каждом старте. Это защита от компрометации upstream-репозитория или MITM-атаки на этапе загрузки.

Модель-инверсия и извлечение данных. LLM могут «запомнить» обучающие данные и воспроизвести их по запросу. Мы не обучаем свои модели на пользовательских данных OGL-Mini обучен на публичных датасетах. Но даже если бы обучали, маскировка PII работает на входе: модель никогда не видит реальные имена и телефоны. Это не просто защита данных - это защита от атак будущего. Если завтра появится надежный метод извлечения обучающих данных через цепочку промптов, наши модели уже не будут содержать персональных данных.

Атаки на управление ключами. Если злоумышленник получит доступ к ENCRYPTION_MASTER_SECRET, он сможет расшифровать все данные. Мы минимизируем этот риск через версионную соль: компрометация мастер-секрета не раскрывает прошлые сессии, если злоумышленник не получил и keyring-документ. Но мы уже думаем о следующем шаге: HSM или KMS для хранения мастер-секрета. Архитектура KeyManager позволяет заменить источник секрета без изменения логики вывода ключей - это ещё один пример криптографической гибкости, которая окупается заранее.

Мы не утверждаем, что наша система неуязвима. Так не бывает. Мы утверждаем, что проектировать нужно так, чтобы адаптироваться. Каждый слой знает о существовании других. Каждое решение документировано. Каждая угроза - даже та, которой ещё нет, оставляет нам пространство для манёвра.

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


Спасибо, что дочитали статью до конца.

Буду рад вашим вопросам и дополнениям!

Комментарии (0)