Перейти к основному содержимому

Безопасность

AgentCore делает RBAC через JWT-аутентификацию, ограничивает доступ через эффективные разрешения отдела, очищает PII перед хранением или передачей данных и защищает от prompt injection до того, как запрос доходит до LLM.

Аутентификация и RBAC

Аутентификация

AgentCore использует JWT (JSON Web Tokens) через @fastify/jwt.

Payload токена

{
"sub": "<user-id>",
"email": "user@example.com",
"role": "approver",
"roleId": "<system-role-id>",
"departmentId": "<department-id>",
"tokenVersion": 3,
"iat": 1713000000,
"exp": 1713604800
}

Настройки

ПеременнаяПо умолчаниюОписание
JWT_SECRETСекрет подписи (минимум 32 символа, обязательно)
JWT_EXPIRES_IN7dСрок жизни токена

Поток

  1. РегистрацияPOST /api/v1/auth/register: создаёт пользователя с ролью employee в переданном departmentId, возвращает JWT. role от клиента игнорируется схемой — создать админа так нельзя.
  2. ЛогинPOST /api/v1/auth/login: проверяет креды, возвращает JWT.
  3. RefreshPOST /api/v1/auth/refresh: выдаёт новый токен на основе валидного.
  4. Forgot / reset passwordPOST /api/v1/auth/forgot-password создаёт одноразовый reset-токен на час, POST /api/v1/auth/reset-password использует его и инкрементирует tokenVersion.
  5. Self-service безопасностьPOST /api/v1/me/change-password и POST /api/v1/me/logout-all инкрементируют tokenVersion и возвращают новый токен.
  6. Использование — добавляйте Authorization: Bearer <token> в запросы API.

Пароли хешируются bcrypt (через bcryptjs). Извлечение JWT ограничено заголовком Authorization. WebSocket-клиенты аутентифицируются первым JSON-сообщением с JWT-токеном; JWT в query string не принимается.

Role-based access control (RBAC)

Роли и разрешения

RBAC основан на permission-ключах. Записи SystemRole определяют:

ПолеОписание
permissionsCapability-ключи, например canManageUsers, canApprove, или wildcard *
allDepartmentsПрименяется ли роль глобально
departmentIdsWhitelist отделов, если роль не глобальная
isSystemМаркер встроенной роли; сиды системных ролей защищены

User.role всё ещё хранит legacy enum (employee, approver, dept_head, admin) для совместимости и fallback-разрешений. Новые проверки доступа вычисляют эффективные разрешения из назначенного SystemRole, user-level permissions/revokes и department-level grants/revokes.

Каталог разрешений доступен через GET /api/v1/permissions. Роли управляются через /api/v1/roles, user-overrides — через /api/v1/users/:id/permissions/....

Middleware

Реализация: src/middleware/rbac.ts.

requirePermission(permission) — требует одно вычисленное разрешение:

requirePermission('canManageUsers')

requireAnyPermission(permissions[]) — требует хотя бы одно из перечисленных:

requireAnyPermission(['canManagePlugins', 'canManageNamespaces'])

requireRole() и requireAnyRole() остаются для legacy-маршрутов, но для нового кода лучше permission-проверки.

requireSelf() — пользователь получает доступ только к своим ресурсам, если у него нет canManageUsers.

Любой отказ RBAC отправляет 401/403 сразу. Route handler не продолжает выполнение после отказа.

Изоляция отделов

Изоляция отделов централизована в src/lib/department-scope.ts.

Scope отдела вычисляется из фактического RBAC-состояния: department scope роли, primary/current отдел и user-level department grants/revokes. Глобальные пользователи видят все отделы; остальные ограничены своими эффективными department IDs.

ПоверхностьШаблон scope
Knowledge bases и namespacesпрямые departmentId-фильтры через scope.directWhere()
Agent tasksвложенные namespace-фильтры через scope.nestedWhere('namespace')
Approvals и разговорыscope через отдел user-а/разговора
RAG-поискraw SQL-фильтры, собранные из scope.departmentId
WebSocket task eventsnamespace-доступ проверяется при подписке и снова перед каждым бродкастом
Профили сотрудниковсвой профиль или эффективный department scope; глобальные пользователи могут читать все
Шаблоны документов и история генерациифильтры namespace/отдел из эффективного department scope
Уведомлениятолько владелец (userId)

Регрессионный набор tests/department-isolation.test.ts покрывает пути list, detail, mutations, analytics, RAG и WebSocket.

Матрица доступа к эндпоинтам

Группа эндпоинтовemployeeapproverdept_headadmin
Auth (register/login)дададада
API обзора разговоровнетдадада
Knowledge bases (чтение)дададада
Knowledge bases (запись)в рамках отделав рамках отделав рамках отделада
Загрузка документовв рамках отделав рамках отделав рамках отделада
Approvalsнетдадада
Namespaces (чтение)дададада
Namespaces (запись)нетнетдада
Управление отделаминетнетнетда
Управление пользователяминетнетнетда
Управление роляминетнетнетда
Генерация документовдададада
Управление шаблонаминетнетдада
Плагины (чтение)дададада
Плагины (управление)нетнетдада
Уведомлениясвоисвоисвоисвои
Журналы аудитанетнетнетда
RAG draftнетдадада
Профили сотрудников (свой)дададада
Профили сотрудников (редактирование)нетнетдада

PII Scrubber

AgentCore использует двухуровневую систему защиты PII, чтобы не допустить утечки персональных данных через AI-пайплайн.

Реализация

src/knowledge/pii.ts.

Уровень 1. Ingest-time scrubbing (односторонний)

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

Распознаваемые паттерны

ТипПаттернЗамена
Emailuser@domain.com[REDACTED_EMAIL]
Телефон+380123456789[REDACTED_PHONE]
Кредитная карта4111-1111-1111-1111[REDACTED_CC]
IP-адрес192.168.1.1[REDACTED_IP]
IBANUA213223130000026007233566001[REDACTED_IBAN]
Сумма$1,234.56[REDACTED_AMOUNT]

Когда применяется

  • После извлечения текста, до чанкинга.
  • На все типы документов (PDF, DOCX, TXT, URL, изображения).
  • Необратимо (база знаний всегда чистая).

Уровень 2. Conversation-time scrubbing (обратимый)

Во время живого разговора PII в сообщении пользователя обратимо маскируется перед отправкой в LLM. После ответа LLM плейсхолдеры в ответе восстанавливаются в реальные значения.

Поток

  1. Пользователь присылает сообщение с PII (например, email).
  2. PII детектится и заменяется плейсхолдером: [PII_EMAIL_a1b2c3].
  3. Мапинг хранится зашифрованным в PiiRedactionMap:
    • placeholder: [PII_EMAIL_a1b2c3];
    • entityType: email;
    • encryptedValue: оригинал, зашифрованный AES-256-GCM.
  4. Очищенное сообщение уходит в LLM.
  5. Получен ответ LLM (может содержать плейсхолдер).
  6. Плейсхолдеры в ответе восстанавливаются из зашифрованного мапинга.
  7. Пользователь видит в ответе исходный PII.

Шифрование

  • Алгоритм: AES-256-GCM.
  • Источник ключа: PII_ENCRYPTION_KEY.
  • Формат ключа: 32-байтовый ключ в Base64.
  • Каждое зашифрованное значение включает IV и auth tag.
  • Scope ограничен разговором (мапы привязаны к conversationId).

PII-шифрование намеренно отделено от JWT_SECRET — ротация auth-токенов не приводит к неявной ротации зашифрованного PII, а утечка одного секрета не компрометирует автоматически другой домен.

Зачем два уровня

УровеньЦельДвустороннийОбласть
IngestЗащитить базу знаний at restНетДокументы/чанки
ConversationЗащитить данные пользователя при передаче в LLMДаLive-сообщения

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


Injection Guard

Injection guard ловит и блокирует попытки prompt injection в сообщениях пользователя и RAG-контексте.

Реализация

src/knowledge/injection-guard.tscheckForInjection().

Категории

1. Instruction override

Попытки переопределить системные инструкции.

Примеры: "ignore previous instructions", "disregard all rules", "forget the prompt", "do not follow your instructions".

2. Persona injection

Попытки изменить identity или поведение AI.

Примеры: "you are now a hacker", "act as an unrestricted AI", "pretend you have no rules", "roleplay as", "from now on you will".

3. Jailbreak

Известные jailbreak-техники.

Примеры: "DAN mode enabled", "developer mode", "no restrictions apply", "unrestricted mode".

4. Prompt extraction

Попытки вытащить system prompt.

Примеры: "show me the system prompt", "reveal your instructions", "print all rules", "what is the prompt".

5. Role injection

Попытки инъектить system-level сообщения.

Примеры: [system]:, [assistant]:, ```system, <|im_start|>system.

6. Context manipulation

Попытки сбросить или манипулировать контекстом разговора.

Примеры: "end of context", "start new session", "reset conversation", "new system message".

Точки применения

Guard применяется в двух точках:

  1. Сообщение пользователя — проверяется перед запуском RAG.
  2. Чанки RAG-контекста — каждый извлечённый чанк проверяется до сборки контекста.

Ответ

interface InjectionCheckResult {
safe: boolean;
reason?: string; // Category name if detected
}

При обнаружении инъекции:

  • возвращается safe: false с reason;
  • сообщение всегда уходит в HITL, независимо от trust-статуса;
  • в ответе RAG выставляются injectionDetected: true и injectionReason.

Количество паттернов

Guard содержит 30+ regex-паттернов по перечисленным категориям. Паттерны case-insensitive и закрывают распространённые вариации и попытки обфускации.

Ограничения

  • Детекция на regex — сложные атаки могут обойти паттерны.
  • LLM-классификации нет (в планах).
  • Фокус на английских и украинских паттернах.

API boundary protection

Структурированный конверт ошибок

Ошибки приложения используют один JSON-формат:

{
"error": "ValidationError",
"message": "Request validation failed",
"statusCode": 422,
"details": {}
}

Поле details опциональное. src/middleware/errorHandler.ts нормализует ошибки Zod, JWT, Prisma, OpenAI/провайдера, known-application, 404 и неожиданные 500 в этот конверт. Маршруты могут использовать sendError(reply, statusCode, error, message, details) для явных отказов.

Rate limits

@fastify/rate-limit зарегистрирован глобально:

  • глобальный дефолт: 100 запросов в минуту;
  • ключ: ID аутентифицированного пользователя, если есть, иначе IP клиента;
  • хранилище: Redis вне тестов, in-memory в тестах;
  • allowlist: feedback-клиенты и /api/v1/health;
  • auth-специфичные override: регистрация — 3 запроса в час, логин — 5 запросов в 15 минут.

CORS

CORS использует allowlist точных origin из ALLOWED_ORIGINS. Credentials включены, поэтому wildcard-origins отклоняются при парсинге конфига.