
Un protocollo wire unificato e security-first per l'accesso agli strumenti e il coordinamento degli agenti. UAP elimina le vulnerabilità CVE-2025-49596 e di avvelenamento degli strumenti MCP utilizzando CapabilityCards firmate con Ed25519, mTLS obbligatorio, autenticazione IdP Keycloak e sandboxing Docker effimero per singola chiamata.
██╗ ██╗ █████╗ ██████╗
██║ ██║██╔══██╗██╔══██╗
██║ ██║███████║██████╔╝
██║ ██║██╔══██║██╔═══╝
╚██████╔╝██║ ██║██║
╚═════╝ ╚═╝ ╚═╝╚═╝
Universal Agent Protocol
Un protocollo wire unificato e security-first per l'accesso agli strumenti, il coordinamento degli agenti e RPC strutturato. Progettato per chiudere CVE-2025-49596 e la classe di vulnerabilità di tool-poisoning e sandbox-escape che MCP ha lasciato strutturalmente aperte.
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"}
Questo avvia Keycloak sulla porta :8080 e il gateway UAP sulla porta :3000. Il gateway impone mTLS, verifica le CapabilityCard firmate Ed25519 ed esegue ogni chiamata di strumento in un container Docker effimero. Nessuna configurazione richiesta oltre alla coppia di chiavi generata.
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)]Layers esagonali — il dominio ha zero dipendenze I/O. Ogni aspetto infrastrutturale si trova dietro un'interfaccia port tipizzata. Sostituisci Docker con WASM, Keycloak con Auth0, Pino con un exporter SIEM — impatto zero sul dominio.
CVE-2025-49596 (tool-poisoning tramite metadati non firmati). Le descrizioni degli strumenti MCP sono mutabili dopo la pubblicazione. Un attaccante può iniettare istruzioni dannose nei nomi o nelle descrizioni degli strumenti dopo il deployment — il client non ha alcun modo di rilevare la manomissione. UAP inserisce tutti i metadati degli strumenti nel payload della CapabilityCard firmata Ed25519. Qualsiasi mutazione dopo la firma invalida la firma e il gateway rifiuta la card prima di eseguire qualsiasi cosa.
CWE-284 / classe sandbox-escape. MCP esegue gli strumenti a livello di processo del server. Un path traversal o un'iniezione di processo in qualsiasi strumento raggiunge il filesystem e la rete dell'host. UAP crea un nuovo container Docker per ogni chiamata — rootfs di sola lettura, CapDrop: ALL, limite di 128 MB di RAM, rete disabilitata per impostazione predefinita. Il container viene distrutto dopo la risposta. Non esiste una superficie d'attacco persistente tra le chiamate.
CWE-287 / autenticazione confused-deputy. MCP agisce come proprio provider OAuth, rendendosi sia server delle risorse che server di autorizzazione. Questo è il classico schema confused-deputy. UAP separa questi ruoli: il gateway è un puro server delle risorse. Keycloak (o qualsiasi IdP esterno) è l'unica autorità. I JWT vengono verificati contro un endpoint JWKS remoto e hanno un tetto massimo di 15 minuti.
Ogni messaggio UAP utilizza lo stesso envelope. Il blocco auth trasporta un JWT con scope e un riferimento alla CapabilityCard firmata dell'agente. Il gateway verifica entrambi prima che la richiesta raggiunga qualsiasi codice applicativo.
{
"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"])
Middleware FastAPI
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
...
La CLI uap-migrate incapsula qualsiasi server MCP come una CapabilityCard UAP firmata in meno di 5 minuti. Idempotente — rieseguirla su un server già migrato non ha alcun effetto.
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
Nomi, descrizioni e schemi dei parametri degli strumenti fanno parte del payload firmato Ed25519. Mutare qualsiasi campo dopo la firma invalida la firma — sconfiggendo strutturalmente il 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..."
Naming dei branch: feat/<sprint>-<task>-<slug> — es. feat/s2-2.1-mtls-keycloak
Ogni modifica segue: branch → npx tsc --noEmit + npx vitest run in locale → commit → PR → squash merge → eliminazione del branch. Nessuna dipendenza dalla CI per lo sviluppo locale.
# 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
Roadmap: Sprint 0 (scaffold) → Sprint 1 (schema + firma + RPC) → Sprint 2 (nucleo di sicurezza) → Sprint 3 (gateway + registry) → Sprint 4 (adattatori bridge) → Sprint 5 (SDK + immagine Docker).
UAP · Universal Agent Protocol · 2026
SPDX-License-Identifier: Apache-2.0
| Layer | Contenuti | Dipendenze |
|---|
| Dominio | UapEnvelope, CapabilityCard, AuditEvent, Task | Nessuna — solo stdlib |
| Applicazione | InvokeToolUseCase, DelegateTaskUseCase, ValidateCardUseCase | Solo porte di dominio |
| Infrastruttura | KeycloakAuthAdapter, DockerSandboxAdapter, Ed25519SignerAdapter, PinoAuditAdapter | Porte dell'app + librerie esterne |
| Trasporto | Parser di frame UAP-RPC, gateway Fastify, gestori SSE/WebSocket | Serializzatori di infrastruttura + dominio |
| Funzionalità | UAP | MCP | A2A | JSON-RPC 2.0 |
|---|
| Autenticazione obbligatoria | ✅ Sempre mTLS + JWT | ❌ Opzionale | ⚠️ Solo API key | ❌ Nessuna |
| Integrità dei metadati degli strumenti | ✅ Payload firmato Ed25519 | ❌ Mutazione possibile dopo la pubblicazione | ❌ Nessuna firma | ❌ Nessuna firma |
| Modello sandbox | ✅ Docker effimero per ogni chiamata | ⚠️ Solo a livello di server | ❌ Nessuno | ❌ Nessuno |
| Scoperta degli agenti | ✅ Hub-and-spoke · N connessioni | ❌ HTTP diretto N² | ⚠️ Basata su DNS | ❌ Nessuna |
| Registro di audit | ✅ Log append-only nativo del protocollo | ❌ Nessuno | ❌ Nessuno | ❌ Nessuno |
| Tracing distribuito | ✅ W3C TraceContext obbligatorio | ❌ Nessuno | ❌ Nessuno | ❌ Nessuno |
| Validazione dello schema | ✅ AJV a livello di trasporto | ⚠️ Zod opzionale | ❌ Nessuna | ❌ Nessuna |
| Limite di durata del token | ✅ 15 minuti imposti | ❌ Non imposto | ❌ Non imposto | ❌ N/A |
| Semantica batch | ✅ seriale · parallela · transazionale | ❌ Non definita | ❌ Nessuna | ⚠️ Ambigua |
| Payload binari | ✅ Frame nativi | ❌ Solo Base64 | ❌ Solo Base64 | ❌ Solo Base64 |
| Modello IdP | ✅ Solo IdP esterno | ❌ Il server è il proprio provider OAuth | ⚠️ Varia | ❌ Nessuno |
| Adattatori bridge | ✅ I bridge MCP + A2A arrivano nella Fase 2 | ❌ Nessun bridge | ❌ Nessun bridge | ❌ Nessun bridge |
| Scalabilità orizzontale | ✅ Stateless · qualsiasi load balancer | ❌ Sessioni sticky | ⚠️ Varia | ❌ Nessuna |
| Variabile | Descrizione | Predefinito |
|---|
KEYCLOAK_URL | URL base di Keycloak | http://localhost:8080 |
KEYCLOAK_REALM | Nome del realm | uap |
UAP_AUDIENCE | Claim audience del JWT | uap-gateway |
UAP_SIGNING_KEY_PATH | Percorso della chiave privata Ed25519 in hex | config/dev.privkey.hex |
UAP_DOCKER_SOCKET | Percorso del socket Docker | /var/run/docker.sock |
UAP_AUDIT_LOG_DIR | Directory del log di audit NDJSON append-only | /var/log/uap |
UAP_GATEWAY_PORT | Porta di ascolto del gateway | 3000 |
UAP_MTLS_CERT | Certificato TLS del server (PEM) | certs/server.crt |
UAP_MTLS_KEY | Chiave privata TLS del server (PEM) | certs/server.key |
UAP_MTLS_CA | Certificato CA per la verifica dei certificati client | certs/ca.crt |
OTEL_EXPORTER_OTLP_ENDPOINT | Endpoint del collector OpenTelemetry | http://localhost:4318 |
REDIS_URL | Redis per il registry multi-istanza (opzionale) | — |
| Pacchetto | Versione | Scopo |
|---|
fastify | ^4.27 | Server HTTP del gateway |
zod | ^3.23 | Schema dell'envelope e inferenza dei tipi |
@noble/curves | ^1.4 | Firma Ed25519 |
jose | ^5.4 | Verifica JWT, client JWKS |
ajv | ^8.16 | Validazione dei parametri a livello di trasporto |
dockerode | ^4.0 | Adapter sandbox zero-trust |
pino | ^9.2 | Log di audit strutturato append-only |
ulid | ^2.3 | ID univoci ordinabili |
@opentelemetry/sdk-node | ^0.52 | Tracing distribuito |
ioredis | ^5.3 | Registry dei servizi multi-istanza |
@modelcontextprotocol/sdk | ^1.0 | Adapter bridge MCP |
httpx (Python) | ^0.27 | Client HTTP asincrono per l'SDK Python |
pydantic (Python) | ^2.7 | Validazione dei tipi Python |