
Единый проводной протокол с приоритетом безопасности (security-first) для доступа к инструментам и координации агентов. UAP устраняет уязвимость CVE-2025-49596 и уязвимости отравления инструментов MCP, используя подписанные Ed25519 карточки возможностей (CapabilityCards), обязательный mTLS, аутентификацию через Keycloak IdP и эфемерную Docker‑песочницу для каждого вызова.
██╗ ██╗ █████╗ ██████╗
██║ ██║██╔══██╗██╔══██╗
██║ ██║███████║██████╔╝
██║ ██║██╔══██║██╔═══╝
╚██████╔╝██║ ██║██║
╚═════╝ ╚═╝ ╚═╝╚═╝
Universal Agent Protocol
Единый сетевой протокол, в котором безопасность — на первом месте, для доступа к инструментам, координации агентов и структурированного RPC. Разработан для закрытия CVE-2025-49596 и класса уязвимостей, связанных с отравлением инструментов (tool-poisoning) и выходом из песочницы (sandbox-escape), которые MCP оставил структурно открытыми.
git clone https://github.com/RajSidwadkar/UAP-protocol
cd UAP-protocol
npm install
npx tsx scripts/generate-keypair.ts
docker compose -f docker-compose.dev.yml up -d
curl http://localhost:3000/health
# {"status":"ok","version":"1.0.0"}
Это запускает Keycloak на :8080 и шлюз UAP на :3000. Шлюз обеспечивает mTLS, проверяет CapabilityCard, подписанные Ed25519, и выполняет каждый вызов инструмента в эфемерном Docker-контейнере. Кроме сгенерированной ключевой пары никакая настройка не требуется.
graph TD
A[Client / Agent SDK] -->|mTLS + JWT + card_sig| B[UAP Gateway<br/>Fastify · port 3000]
B --> C{8-Stage Pipeline}
C --> C1[1 · Frame parse<br/>UapMessageFactory]
C1 --> C2[2 · Schema validate<br/>AJV against schema_ref]
C2 --> C3[3 · Token verify<br/>Keycloak JWKS · max 15 min]
C3 --> C4[4 · Scope enforce<br/>PermissionEnforcer]
C4 --> C5[5 · Card verify<br/>Ed25519 signature check]
C5 --> C6[6 · Sandbox execute<br/>Docker · CapDrop ALL · 128 MB]
C6 --> C7[7 · Audit emit<br/>AuditEventBus · non-blocking]
C7 --> C8[8 · Response<br/>UapResponseEnvelope]
B --> R[(Service Registry<br/>InMemory · Redis)]
B --> KC[(Keycloak IdP<br/>OAuth 2.1 · PKCE)]
B --> OT[(OpenTelemetry<br/>W3C TraceContext)]
B --> AU[(Audit Log<br/>append-only NDJSON)]
Гексагональные слои — в домене нет зависимостей ввода-вывода. Каждый инфраструктурный компонент находится за типизированным порт-интерфейсом. Замените Docker на WASM, Keycloak на Auth0, Pino на SIEM-экспортёр — влияние на домен нулевое.
CVE-2025-49596 (отравление инструментов через неподписанные метаданные). Описания инструментов MCP можно менять после публикации. Злоумышленник может внедрить вредоносные инструкции в имена или описания инструментов после развёртывания — и у клиента нет способа обнаружить подмену. UAP помещает все метаданные инструментов в полезную нагрузку CapabilityCard, подписанную Ed25519. Любое изменение после подписания делает подпись недействительной, поэтому шлюз отклоняет карту до выполнения каких-либо действий.
CWE-284 / класс sandbox-escape. MCP выполняет инструменты на уровне процесса сервера. Path traversal или внедрение процесса в любом инструменте открывают файловую систему и сеть хоста. UAP создаёт новый Docker-контейнер для каждого вызова: rootfs только для чтения, CapDrop: ALL, лимит ОЗУ 128 МБ, сеть отключена по умолчанию. Контейнер уничтожается сразу после ответа. Между вызовами не остаётся постоянной поверхности атаки.
CWE-287 / аутентификация confused-deputy. MCP выступает собственным OAuth-провайдером, то есть одновременно является и сервером ресурсов, и сервером авторизации. Это классический паттерн confused-deputy. UAP разделяет эти роли: шлюз — чистый сервер ресурсов, а Keycloak (или любой внешний IdP) — единственный источник полномочий. JWT проверяются через удалённую точку JWKS и жёстко ограничены 15 минутами.
Все сообщения UAP используют один и тот же конверт. Блок auth содержит JWT с ограниченной областью действия и ссылку на подписанную CapabilityCard агента. Шлюз проверяет оба элемента до того, как запрос попадёт в какой-либо прикладной код.
{
"uap": {
"version": "1.0",
"type": "tool_call",
"id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
"trace": {
"traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
},
"auth": {
"token": "eyJhbGciOiJSUzI1NiJ9...",
"scope": ["tool:read"],
"card_sig": "ed25519:a1b2c3d4..."
}
},
"method": "tools/invoke",
"schema_ref": "uap:tool.invoke/v1",
"params": {
"tool_id": "db:query",
"input": { "sql": "SELECT 1" }
},
"ack": true
}
UAP-protocol/
├── packages/
│ ├── gateway/ @uap/gateway — Fastify-based UAP Gateway
│ ├── sdk-ts/ @uap/sdk-ts — TypeScript client + server SDK
│ └── sdk-py/ uap-sdk — Python async SDK (httpx + FastAPI)
├── scripts/
│ ├── generate-keypair.ts — Ed25519 keypair for local dev
│ └── keycloak-bootstrap.ts — Idempotent Keycloak realm setup
└── docker/
└── sandbox/ — Base image for zero-trust tool execution
import { UapClient, ClientCredentialsTokenProvider } from "@uap/sdk-ts";
const client = new UapClient({
gatewayUrl: "https://gateway.example.com",
tokenProvider: new ClientCredentialsTokenProvider({
tokenUrl: process.env.TOKEN_URL!,
clientId: process.env.CLIENT_ID!,
clientSecret: process.env.CLIENT_SECRET!,
}),
});
const result = await client.invokeTool(
"db:query",
{ sql: "SELECT * FROM users LIMIT 10" },
["tool:read"]
);
const task = await client.delegateTask(
"summarizer",
{ text: "..." },
["task:submit"]
);
from uap_sdk.client import UapClient, UapClientOptions
from uap_sdk.token import ClientCredentialsTokenProvider
async with UapClient(UapClientOptions(
gateway_url="https://gateway.example.com",
token_provider=ClientCredentialsTokenProvider(
token_url=os.environ["TOKEN_URL"],
client_id=os.environ["CLIENT_ID"],
client_secret=os.environ["CLIENT_SECRET"],
)
)) as client:
result = await client.invoke_tool("db:query", {"sql": "SELECT 1"}, ["tool:read"])
FastAPI middleware
from uap_sdk.middleware import uap_auth
@app.post("/summarize")
@uap_auth(scope=["task:submit"])
async def summarize(request: Request):
claims = request.state.uap_claims # typed AuthClaims
...
CLI-инструмент uap-migrate оборачивает любой MCP-сервер в подписанную UAP CapabilityCard менее чем за 5 минут. Инструмент идемпотентен — повторный запуск на уже мигрированном сервере не выполняет никаких действий.
node dist/cli/migrate.js mcp http://localhost:3001 \
--issuer did:uap:my-org \
--key config/dev.privkey.hex \
--out ./uap-cards
# → ./uap-cards/did:uap:my-org.card.json
Имена инструментов, их описания и схемы параметров входят в полезную нагрузку, подписанную Ed25519. Изменение любого поля после подписания делает подпись недействительной — это структурно исключает отравление инструментов (tool-poisoning).
const signed = await signer.sign({
issuer: "did:uap:my-agent",
version: "1.0.0",
tools: [{
id: "db:query",
description: "Run a read-only SQL query",
inputSchema: { type: "object", required: ["sql"] },
scopes: ["tool:read"]
}],
scopes: ["tool:read"],
issuedAt: Date.now(),
expiresAt: Date.now() + 86_400_000 * 30
});
// signed.signature === "ed25519:a1b2c3..."
Именование веток: feat/<sprint>-<task>-<slug> — например, feat/s2-2.1-mtls-keycloak
Каждое изменение проходит путь: ветка → npx tsc --noEmit + npx vitest run локально → коммит → PR → squash merge → удаление ветки. Для локальной разработки CI не требуется.
# Run all tests before committing
cd packages/gateway && npx vitest run
cd packages/sdk-ts && npx vitest run
cd packages/sdk-py && python -m pytest tests/ -v
Дорожная карта: Спринт 0 (каркас) → Спринт 1 (схема + подпись + RPC) → Спринт 2 (ядро безопасности) → Спринт 3 (шлюз + реестр) → Спринт 4 (мостовые адаптеры) → Спринт 5 (SDK + Docker-образ).
UAP · Универсальный агентный протокол · 2026
SPDX-License-Identifier: Apache-2.0
| Слой | Содержимое | Зависимости |
|---|
| Домен | UapEnvelope, CapabilityCard, AuditEvent, Task | Нет — только стандартная библиотека |
| Приложение | InvokeToolUseCase, DelegateTaskUseCase, ValidateCardUseCase | Только порты домена |
| Инфраструктура | KeycloakAuthAdapter, DockerSandboxAdapter, Ed25519SignerAdapter, PinoAuditAdapter | Порты приложения + внешние библиотеки |
| Транспорт | Парсер кадров UAP-RPC, шлюз Fastify, обработчики SSE/WebSocket | Инфраструктура + сериализаторы домена |
| Возможность | UAP | MCP | A2A | JSON-RPC 2.0 |
|---|
| Обязательная аутентификация | ✅ mTLS + JWT всегда | ❌ Необязательна | ⚠️ Только API-ключ | ❌ Нет |
| Целостность метаданных инструментов | ✅ Полезная нагрузка, подписанная Ed25519 | ❌ Возможно изменение после публикации | ❌ Без подписи | ❌ Без подписи |
| Модель песочницы | ✅ Эфемерный Docker на каждый вызов | ⚠️ Только на уровне сервера | ❌ Нет | ❌ Нет |
| Обнаружение агентов | ✅ «Звезда» · N подключений | ❌ Прямой HTTP N² | ⚠️ На основе DNS | ❌ Нет |
| Журнал аудита | ✅ Встроенный в протокол журнал append-only | ❌ Нет | ❌ Нет | ❌ Нет |
| Распределённая трассировка | ✅ Обязательный W3C TraceContext | ❌ Нет | ❌ Нет | ❌ Нет |
| Проверка схемы | ✅ AJV на транспортном уровне | ⚠️ Необязательный Zod | ❌ Нет | ❌ Нет |
| Максимальный срок жизни токена | ✅ Принудительно 15 минут | ❌ Не контролируется | ❌ Не контролируется | ❌ N/A |
| Семантика пакетных операций | ✅ последовательно · параллельно · транзакционно | ❌ Не определена | ❌ Нет | ⚠️ Неоднозначна |
| Бинарные полезные нагрузки | ✅ Нативные кадры | ❌ Только Base64 | ❌ Только Base64 | ❌ Только Base64 |
| Модель IdP | ✅ Только внешний IdP | ❌ Сервер сам является OAuth-провайдером | ⚠️ Зависит от реализации | ❌ Нет |
| Мостовые адаптеры | ✅ Мосты MCP + A2A выходят в Phase 2 | ❌ Моста нет | ❌ Моста нет | ❌ Моста нет |
| Горизонтальное масштабирование | ✅ Без состояния · любой балансировщик нагрузки | ❌ Липкие сессии | ⚠️ Зависит от реализации | ❌ Нет |
| Переменная | Описание | По умолчанию |
|---|
KEYCLOAK_URL | Базовый URL Keycloak | http://localhost:8080 |
KEYCLOAK_REALM | Имя realm | uap |
UAP_AUDIENCE | Утверждение audience в JWT | uap-gateway |
UAP_SIGNING_KEY_PATH | Путь к hex-файлу закрытого ключа Ed25519 | config/dev.privkey.hex |
UAP_DOCKER_SOCKET | Путь к Docker-сокету | /var/run/docker.sock |
UAP_AUDIT_LOG_DIR | Каталог журнала аудита NDJSON в режиме append-only | /var/log/uap |
UAP_GATEWAY_PORT | Порт прослушивания шлюза | 3000 |
UAP_MTLS_CERT | Сертификат TLS сервера (PEM) | certs/server.crt |
UAP_MTLS_KEY | Закрытый ключ TLS сервера (PEM) | certs/server.key |
UAP_MTLS_CA | Сертификат ЦС для проверки клиентских сертификатов | certs/ca.crt |
OTEL_EXPORTER_OTLP_ENDPOINT | Конечная точка коллектора OpenTelemetry | http://localhost:4318 |
REDIS_URL | Redis для реестра нескольких инстансов (необязательно) | — |
| Пакет | Версия | Назначение |
|---|
fastify | ^4.27 | HTTP-сервер шлюза |
zod | ^3.23 | Схема конверта и вывод типов |
@noble/curves | ^1.4 | Подпись Ed25519 |
jose | ^5.4 | Проверка JWT, JWKS-клиент |
ajv | ^8.16 | Проверка параметров на транспортном уровне |
dockerode | ^4.0 | Адаптер песочницы с нулевым доверием |
pino | ^9.2 | Структурированный журнал аудита в режиме append-only |
ulid | ^2.3 | Сортируемые уникальные идентификаторы |
@opentelemetry/sdk-node | ^0.52 | Распределённая трассировка |
ioredis | ^5.3 | Реестр сервисов для нескольких инстансов |
@modelcontextprotocol/sdk | ^1.0 | Мостовой адаптер MCP |
httpx (Python) | ^0.27 | Асинхронный HTTP-клиент для Python SDK |
pydantic (Python) | ^2.7 | Проверка типов в Python |