Безопасность
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_IN | 7d | Срок жизни токена |
Поток
- Регистрация —
POST /api/v1/auth/register: создаёт пользователя с рольюemployeeв переданномdepartmentId, возвращает JWT.roleот клиента игнорируется схемой — создать админа так нельзя. - Логин —
POST /api/v1/auth/login: проверяет креды, возвращает JWT. - Refresh —
POST /api/v1/auth/refresh: выдаёт новый токен на основе валидного. - Forgot / reset password —
POST /api/v1/auth/forgot-passwordсоздаёт одноразовый reset-токен на час,POST /api/v1/auth/reset-passwordиспользует его и инкрементируетtokenVersion. - Self-service безопасность —
POST /api/v1/me/change-passwordиPOST /api/v1/me/logout-allинкрементируютtokenVersionи возвращают новый токен. - Использование — добавляйте
Authorization: Bearer <token>в запросы API.
Пароли хешируются bcrypt (через bcryptjs). Извлечение JWT ограничено заголовком Authorization. WebSocket-клиенты аутентифицируются первым JSON-сообщением с JWT-токеном; JWT в query string не принимается.
Role-based access control (RBAC)
Роли и разрешения
RBAC основан на permission-ключах. Записи SystemRole определяют:
| Поле | Описание |
|---|---|
permissions | Capability-ключи, например canManageUsers, canApprove, или wildcard * |
allDepartments | Применяется ли роль глобально |
departmentIds | Whitelist отделов, если роль не глобальная |
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 events | namespace-доступ проверяется при подписке и снова перед каждым бродкастом |
| Профили сотрудников | свой профиль или эффективный department scope; глобальные пользователи могут читать все |
| Шаблоны документов и история генерации | фильтры namespace/отдел из эффективного department scope |
| Уведомления | только владелец (userId) |
Регрессионный набор tests/department-isolation.test.ts покрывает пути list, detail, mutations, analytics, RAG и WebSocket.
Матрица доступа к эндпоинтам
| Группа эндпоинтов | employee | approver | dept_head | admin |
|---|---|---|---|---|
| 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 необратимо маскируется до того, как текст попадёт в базу знаний. Это гарантирует, что вектор-хранилище и содержимое чанков никогда не содержат сырых персональных данных.
Распознаваемые паттерны
| Тип | Паттерн | Замена |
|---|---|---|
user@domain.com | [REDACTED_EMAIL] | |
| Телефон | +380123456789 | [REDACTED_PHONE] |
| Кредитная карта | 4111-1111-1111-1111 | [REDACTED_CC] |
| IP-адрес | 192.168.1.1 | [REDACTED_IP] |
| IBAN | UA213223130000026007233566001 | [REDACTED_IBAN] |
| Сумма | $1,234.56 | [REDACTED_AMOUNT] |
Когда применяется
- После извлечения текста, до чанкинга.
- На все типы документов (PDF, DOCX, TXT, URL, изображения).
- Необратимо (база знаний всегда чистая).
Уровень 2. Conversation-time scrubbing (обратимый)
Во время живого разговора PII в сообщении пользователя обратимо маскируется перед отправкой в LLM. После ответа LLM плейсхолдеры в ответе восстанавливаются в реальные значения.
Поток
- Пользователь присылает сообщение с PII (например, email).
- PII детектится и заменяется плейсхолдером:
[PII_EMAIL_a1b2c3]. - Мапинг хранится зашифрованным в
PiiRedactionMap:placeholder:[PII_EMAIL_a1b2c3];entityType:email;encryptedValue: оригинал, зашифрованный AES-256-GCM.
- Очищенное сообщение уходит в LLM.
- Получен ответ LLM (может содержать плейсхолдер).
- Плейсхолдеры в ответе восстанавливаются из зашифрованного мапинга.
- Пользователь видит в ответе исходный 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.ts — checkForInjection().
Категории
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 применяется в двух точках:
- Сообщение пользователя — проверяется перед запуском RAG.
- Чанки 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 отклоняются при парсинге конфига.