Перейти до основного вмісту

Безпека

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_IN7dTTL токена

Flow

  1. Реєстрація: POST /api/v1/auth/register — створює employee-користувача у вказаному departmentId, повертає JWT. role, переданий клієнтом, ігнорується схемою і не дає можливості створити адміна.
  2. Логін: POST /api/v1/auth/login — перевіряє креденшли, повертає JWT.
  3. Refresh: POST /api/v1/auth/refresh — видає новий токен на основі валідного.
  4. Forgot/reset password: POST /api/v1/auth/forgot-password створює reset-токен на 1 годину, а 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 побудований на дозволах. Записи SystemRole визначають:

ПолеОпис
permissionsКлючі capability: canManageUsers, canApprove або *
allDepartmentsЧи діє роль глобально
departmentIdsWhitelist відділів, якщо роль не глобальна
isSystemМаркер вбудованої ролі; slug-и системних ролей захищені

User.role все ще тримає legacy enum-роль (employee, approver, dept_head, admin) для backward compatibility і fallback-дозволів. Нові перевірки доступу обчислюють ефективні дозволи з призначеної SystemRole, user-level grant/revoke та department-level grant/revoke.

Каталог дозволів доступний через GET /api/v1/permissions. Менеджмент ролей — через /api/v1/roles, перевизначення на рівні користувача — через /api/v1/users/:id/permissions/....

Middleware

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

requirePermission(permission) — вимагає один обчислений дозвіл:

requirePermission('canManageUsers')

requireAnyPermission(permissions[]) — вимагає хоча б один із:

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

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

requireSelf() — користувач має доступ лише до власних ресурсів, якщо у нього нема canManageUsers.

Усі RBAC-відмови повертаються миттєво після відправки 401/403-відповіді. Route-хендлери не продовжують роботу після відмови у перевірці.

Ізоляція відділу

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

Department scope виводиться з ефективного RBAC-стану: scope ролі, основний/поточний відділ та user-level grant/revoke відділів. Користувачі з глобальним scope бачать усі відділи; усі інші обмежені своїми ефективними department id.

ПоверхняЯк застосовується scope
Бази знань і простори іменпрямі departmentId-фільтри через scope.directWhere()
Agent tasksвкладені namespace-фільтри через scope.nestedWhere('namespace')
Схвалення та розмовиdepartment-scope user/conversation
RAG retrievalraw SQL-фільтри, що беруться зі scope.departmentId
WebSocket agent task eventsnamespace-доступ перевіряється на підписці й повторно перед кожним бродкастом
Профілі співробітниківвласний профіль або ефективний department scope; глобальні користувачі читають усі
Шаблони документів і історія генераційnamespace/department-фільтри з ефективного department scope
Сповіщеннятільки власник (userId)

Регресійний набір у tests/department-isolation.test.ts покриває list, detail, mutation, analytics, RAG і WebSocket-шляхи.

Матриця доступу до ендпоінтів

Група ендпоінтівemployeeapproverdept_headadmin
Auth (register/login)тактактактак
Conversations review APIнітактактак
Knowledge bases (read)тактактактак
Knowledge bases (write)scopescopescopeтак
Document uploadscopescopescopeтак
Approvalsнітактактак
Namespaces (read)тактактактак
Namespaces (write)нінітактак
Department managementнінінітак
User managementнінінітак
Role managementнінінітак
Document generationтактактактак
Document template managementнінітактак
Plugins (view)тактактактак
Plugins (manage)нінітактак
Notificationsownownownown
Audit logsнінінітак
RAG draftнітактактак
Employee profiles (own)тактактактак
Employee profiles (edit)нінітактак

PII Scrubber

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

Реалізація

src/knowledge/pii.ts

Рівень 1: scrubbing під час ingestion (незворотний)

На ingestion документа PII незворотно маскується перед тим, як текст потрапляє в базу знань. Це гарантує, що векторне сховище і контент чанків ніколи не містять сирих персональних даних.

Виявлювані патерни

ТипПатернЗаміна
Emailuser@domain.com[REDACTED_EMAIL]
Phone+380123456789[REDACTED_PHONE]
Credit card4111-1111-1111-1111[REDACTED_CC]
IP address192.168.1.1[REDACTED_IP]
IBANUA213223130000026007233566001[REDACTED_IBAN]
Amount$1,234.56[REDACTED_AMOUNT]

Коли застосовується

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

Рівень 2: scrubbing під час розмови (зворотний)

У живих розмовах PII користувача зворотно маскується перед відправкою до LLM. Після отримання відповіді LLM плейсхолдери у відповіді відновлюються до реальних значень.

Flow

  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: per-conversation (мапи повʼязані з conversationId)

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

Навіщо два рівні?

РівеньПризначенняЗворотний?Scope
IngestionЗахист бази знань at-restНіДокументи/чанки
ConversationЗахист користувацьких даних у transit до LLMТакЖиві повідомлення

PII у базі знань видаляється назавжди — ці документи зберігаються нескінченно. PII у розмові має відновлюватись, щоб користувач отримав природну відповідь.


Injection Guard

Injection guard виявляє та блокує спроби prompt injection у повідомленнях користувача і в RAG-контексті.

Реалізація

src/knowledge/injection-guard.tscheckForInjection()

Категорії виявлення

1. Перевизначення інструкцій

Спроби перевизначити системні інструкції.

Приклади: "ignore previous instructions", "disregard all rules", "forget the prompt", "don't follow your instructions"

2. Інʼєкція персони

Спроби змінити персону або поведінку 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", "unrestricted mode"

4. Prompt extraction

Спроби витягти system prompt.

Приклади: "show me your system prompt", "reveal your instructions", "print out all rules", "what's your prompt"

5. Role injection

Спроби вставити system-level повідомлення.

Приклади: [system]:, [assistant]:, ```system, <|im_start|>system

6. Маніпуляції контекстом

Спроби скинути або підмінити контекст розмови.

Приклади: "end of context", "start new session", "reset the conversation", "new system message"

Точки застосування

Guard діє у двох точках:

  1. Повідомлення користувача — перевіряється перед виконанням RAG-запиту
  2. RAG context chunks — кожен retrieved-чанк перевіряється перед збиранням контексту

Відповідь

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

Коли injection виявлено:

  • повертається safe: false з reason виявлення;
  • повідомлення завжди йде на HITL незалежно від trust-статусу;
  • RAG-відповідь містить injectionDetected: true та injectionReason.

Покриття патернами

Guard містить 30+ regex-патернів, що покривають категорії вище. Патерни case-insensitive і розраховані виловлювати поширені варіанти та спроби обфускації.

Обмеження

  • Regex-based виявлення — складні атаки можуть обійти патерни
  • Немає LLM-based класифікації (заплановано на майбутнє)
  • Фокус на англійській та українській мовах

API Boundary Protection

Структурований error envelope

Помилки застосунку мають єдину JSON-форму:

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

Поле details опційне. src/middleware/errorHandler.ts нормалізує Zod, JWT, Prisma, OpenAI/provider, відомі app-помилки, 404 та неочікувані 500 у цей envelope. Маршрути можуть використовувати sendError(reply, statusCode, error, message, details) для явних помилок.

Rate limiting

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

  • глобальний дефолтний ліміт: 100 запитів на хвилину;
  • ключ: authenticated user id, якщо є, інакше клієнтський IP;
  • сховище: Redis у не-тестовому режимі, in-memory у тестах;
  • whitelist: loopback-клієнти та /api/v1/health;
  • специфічні override для auth: реєстрація — 3 запити/годину, логін — 5 запитів/15 хвилин.

CORS

CORS використовує whitelist по точних origin із ALLOWED_ORIGINS. Credentials увімкнені, тож wildcard-origin відхиляється ще на парсингу конфігурації.