
A unified, security-first wire protocol for tool access and agent coordination. UAP eliminates CVE-2025-49596 and MCP tool-poisoning vulnerabilities using Ed25519-signed CapabilityCards, mandatory mTLS, Keycloak IdP authentication, and per-call ephemeral Docker sandboxing.
██╗ ██╗ █████╗ ██████╗
██║ ██║██╔══██╗██╔══██╗
██║ ██║███████║██████╔╝
██║ ██║██╔══██║██╔═══╝
╚██████╔╝██║ ██║██║
╚═════╝ ╚═╝ ╚═╝╚═╝
Universal Agent Protocol
A unified, security-first wire protocol for tool access, agent coordination, and structured RPC. Built to close CVE-2025-49596 and the class of tool-poisoning and sandbox-escape vulnerabilities that MCP left structurally open.
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"}
Das startet Keycloak auf :8080 und das UAP Gateway auf :3000. Das Gateway erzwingt mTLS, verifiziert Ed25519-signierte CapabilityCards und führt jeden Tool-Aufruf in einem ephemeren Docker-Container aus. Es ist keine Konfiguration über das generierte Schlüsselpaar hinaus erforderlich.
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)]
Hexagonale Schichten – die Domäne hat null I/O-Abhängigkeiten. Jedes Infrastruktur-Problem sitzt hinter einer typisierten Port-Schnittstelle. Tausche Docker gegen WASM, Keycloak gegen Auth0, Pino gegen einen SIEM-Exporter – null Auswirkungen auf die Domäne.
CVE-2025-49596 (Tool-Poisoning durch unsignierte Metadaten). MCP-Toolbeschreibungen sind nach der Veröffentlichung veränderbar. Ein Angreifer kann nach der Bereitstellung bösartige Anweisungen in Tool-Namen oder -Beschreibungen einschleusen – der Client hat keine Möglichkeit, Manipulationen zu erkennen. UAP legt alle Tool-Metadaten in die Ed25519-signierte CapabilityCard-Nutzlast. Jede Mutation nach der Signatur macht die Signatur ungültig und das Gateway lehnt die Karte ab, bevor etwas ausgeführt wird.
CWE-284 / Sandbox-Escape-Klasse. MCP führt Tools auf Server-Prozessebene aus. Ein Path Traversal oder eine Prozessinjektion in einem beliebigen Tool erreicht das Host-Dateisystem und -Netzwerk. UAP erstellt pro Aufruf einen neuen Docker-Container – read-only rootfs, CapDrop: ALL, 128 MB RAM-Limit, Netzwerk standardmäßig deaktiviert. Der Container wird nach der Antwort zerstört. Es gibt keine persistente Angriffsfläche zwischen den Aufrufen.
CWE-287 / Confused-Deputy-Authentifizierung. MCP fungiert als eigener OAuth-Anbieter und ist sowohl Ressourcenserver als auch Autorisierungsserver. Dies ist das klassische Confused-Deputy-Muster. UAP trennt diese Rollen: Das Gateway ist ein reiner Ressourcenserver. Keycloak (oder ein beliebiger externer IdP) ist die alleinige Autorität. JWTs werden gegen einen entfernten JWKS-Endpunkt verifiziert und auf 15 Minuten hart begrenzt.
Jede UAP-Nachricht verwendet denselben Envelope. Der auth-Block trägt ein begrenztes JWT und einen Verweis auf die signierte CapabilityCard des Agents. Das Gateway verifiziert beide, bevor die Anfrage den Anwendungscode erreicht.
{
"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-basiertes 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-Schlüsselpaar für lokale Entwicklung
│ └── keycloak-bootstrap.ts — Idempotentes Keycloak-Realm-Setup
└── docker/
└── sandbox/ — Basis-Image für Zero-Trust-Tool-Ausführung
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
...
Die CLI uap-migrate kapselt jeden MCP-Server in unter 5 Minuten als signierte UAP-CapabilityCard. Idempotent – eine erneute Ausführung auf einem bereits migrierten Server ist ein No-Op.
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
Tool-Namen, -Beschreibungen und Parameterschemata sind Teil der Ed25519-signierten Nutzlast. Das Mutieren eines Feldes nach der Signatur macht die Signatur ungültig – was Tool-Poisoning strukturell verhindert.
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..."
Branch-Benennung: feat/<sprint>-<task>-<slug> – z.B. feat/s2-2.1-mtls-keycloak
Jede Änderung folgt: Branch → npx tsc --noEmit + npx vitest run lokal → Commit → PR → Squash-Merge → Branch löschen. Keine CI-Abhängigkeit für lokale Entwicklung.
# Alle Tests vor dem Commit ausführen
cd packages/gateway && npx vitest run
cd packages/sdk-ts && npx vitest run
cd packages/sdk-py && python -m pytest tests/ -v
Roadmap: Sprint 0 (Grundgerüst) → Sprint 1 (Schema + Signierung + RPC) → Sprint 2 (Sicherheitskern) → Sprint 3 (Gateway + Registry) → Sprint 4 (Bridge-Adapter) → Sprint 5 (SDKs + Docker-Image).
UAP · Universal Agent Protocol · 2026
SPDX-License-Identifier: Apache-2.0
| Schicht | Inhalt | Abhängigkeiten |
|---|
| Domäne | UapEnvelope, CapabilityCard, AuditEvent, Task | Keine – nur Stdlib |
| Anwendung | InvokeToolUseCase, DelegateTaskUseCase, ValidateCardUseCase | Nur Domänen-Ports |
| Infrastruktur | KeycloakAuthAdapter, DockerSandboxAdapter, Ed25519SignerAdapter, PinoAuditAdapter | App-Ports + externe Bibliotheken |
| Transport | UAP-RPC frame parser, Fastify gateway, SSE/WebSocket handlers | Infrastruktur + Domänen-Serialisierer |
| Merkmal | UAP | MCP | A2A | JSON-RPC 2.0 |
|---|
| Erforderliche Authentifizierung | ✅ mTLS + JWT immer | ❌ Optional | ⚠️ Nur API-Schlüssel | ❌ Keine |
| Metadatenintegrität von Tools | ✅ Ed25519-signierte Nutzlast | ❌ Post-Publish-Mutation möglich | ❌ Keine Signatur | ❌ Keine Signatur |
| Sandbox-Modell | ✅ Pro Aufruf ephemerer Docker | ⚠️ Nur Server-Ebene | ❌ Keine | ❌ Keine |
| Agentenerkennung | ✅ Hub-and-Spoke · N Verbindungen | ❌ N² direkt HTTP | ⚠️ DNS-basiert | ❌ Keine |
| Prüfpfad | ✅ Protokoll-eigener Nur-Anhängen-Log | ❌ Keine | ❌ Keine | ❌ Keine |
| Verteilte Ablaufverfolgung | ✅ W3C TraceContext verpflichtend | ❌ Keine | ❌ Keine | ❌ Keine |
| Schema-Validierung | ✅ AJV auf Transportebene | ⚠️ Optionales Zod | ❌ Keine | ❌ Keine |
| Token-Lebensdauerbegrenzung | ✅ 15 Min. erzwungen | ❌ Nicht erzwungen | ❌ Nicht erzwungen | ❌ N/A |
| Batch-Semantik | ✅ seriell · parallel · transaktional | �️ Nicht definiert | ❌ Keine | ⚠️ Mehrdeutig |
| Binäre Nutzlasten | ✅ Native Frames | ❌ Nur Base64 | ❌ Nur Base64 | ❌ Nur Base64 |
| IdP-Modell | ✅ Nur externer IdP | ❌ Server ist eigener OAuth-Anbieter | ⚠️ Variiert | ❌ Keine |
| Bridge-Adapter | ✅ MCP + A2A Bridges in Phase 2 | ❌ Keine Bridge | ❌ Keine Bridge | ❌ Keine Bridge |
| Horizontale Skalierung | ✅ Zustandslos · jeder Load Balancer | ❌ Sticky Sessions | ⚠️ Variiert | ❌ Keine |
| Variable | Beschreibung | Standard |
|---|
KEYCLOAK_URL | Keycloak-Basis-URL | http://localhost:8080 |
KEYCLOAK_REALM | Realm-Name | uap |
UAP_AUDIENCE | JWT-Audience-Anspruch | uap-gateway |
UAP_SIGNING_KEY_PATH | Pfad zum Ed25519-privaten Schlüssel (Hex) | config/dev.privkey.hex |
UAP_DOCKER_SOCKET | Docker-Socket-Pfad | /var/run/docker.sock |
UAP_AUDIT_LOG_DIR | Nur-Anhängen-Audit-NDJSON-Log-Verzeichnis | /var/log/uap |
UAP_GATEWAY_PORT | Gateway-Listening-Port | 3000 |
UAP_MTLS_CERT | Server-TLS-Zertifikat (PEM) | certs/server.crt |
UAP_MTLS_KEY | Server-TLS-privater Schlüssel (PEM) | certs/server.key |
UAP_MTLS_CA | CA-Zertifikat für Client-Zertifikatsverifikation | certs/ca.crt |
OTEL_EXPORTER_OTLP_ENDPOINT | OpenTelemetry-Collector-Endpunkt | http://localhost:4318 |
REDIS_URL | Redis für Multi-Instanz-Registry (optional) | — |
| Package | Version | Zweck |
|---|
fastify | ^4.27 | Gateway-HTTP-Server |
zod | ^3.23 | Envelope-Schema und Typinferenz |
@noble/curves | ^1.4 | Ed25519-Signierung |
jose | ^5.4 | JWT-Verifikation, JWKS-Client |
ajv | ^8.16 | Transportebenen-Parametervalidierung |
dockerode | ^4.0 | Zero-Trust-Sandbox-Adapter |
pino | ^9.2 | Nur-Anhängen-strukturiertes Audit-Log |
ulid | ^2.3 | Sortierbare eindeutige IDs |
@opentelemetry/sdk-node | ^0.52 | Verteilte Ablaufverfolgung |
ioredis | ^5.3 | Multi-Instanz-Service-Registry |
@modelcontextprotocol/sdk | ^1.0 | MCP-Bridge-Adapter |
httpx (Python) | ^0.27 | Asynchroner HTTP-Client für das Python-SDK |
pydantic (Python) | ^2.7 | Python-Typvalidierung |