
도구 접근 및 에이전트 조정을 위한 통합 보안 우선 와이어 프로토콜. UAP는 Ed25519 서명 CapabilityCards, 필수 mTLS, Keycloak IdP 인증, 호출별 임시 Docker 샌드박싱을 통해 CVE-2025-49596 및 MCP 도구 중독(tool-poisoning) 취약점을 제거합니다.
██╗ ██╗ █████╗ ██████╗
██║ ██║██╔══██╗██╔══██╗
██║ ██║███████║██████╔╝
██║ ██║██╔══██║██╔═══╝
╚██████╔╝██║ ██║██║
╚═════╝ ╚═╝ ╚═╝╚═╝
Universal Agent Protocol
도구 액세스, 에이전트 조정, 구조화된 RPC를 위한 통합 보안 우선 와이어 프로토콜. MCP가 구조적으로 열어 둔 CVE-2025-49596과 도구 중독(tool-poisoning) 및 샌드박스 탈출 계열 취약점을 해결하기 위해 설계되었습니다.
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를 적용하고 Ed25519 서명된 CapabilityCard를 검증하며 모든 도구 호출을 임시 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)]헥사고날 레이어 — 도메인은 I/O 의존성이 전혀 없습니다. 모든 인프라 관심사는 타입화된 포트 인터페이스 뒤에 위치합니다. Docker를 WASM으로, Keycloak을 Auth0으로, Pino를 SIEM 익스포터로 교체해도 도메인에는 전혀 영향이 없습니다.
CVE-2025-49596(서명되지 않은 메타데이터를 통한 도구 중독). MCP 도구 설명은 게시 후에도 변경될 수 있습니다. 공격자는 배포 후 도구 이름이나 설명에 악성 지침을 주입할 수 있으며, 클라이언트는 변조를 감지할 방법이 없습니다. UAP는 모든 도구 메타데이터를 Ed25519 서명된 CapabilityCard 페이로드 안에 넣습니다. 서명 이후의 모든 변경은 서명을 무효화하며, 게이트웨이는 무엇이든 실행하기 전에 카드를 거부합니다.
CWE-284 / 샌드박스 탈출 계열. MCP는 서버 프로세스 수준에서 도구를 실행합니다. 어떤 도구에서든 경로 탐색이나 프로세스 주입이 발생하면 호스트 파일시스템과 네트워크까지 도달합니다. UAP는 호출마다 새로운 Docker 컨테이너를 생성합니다. 읽기 전용 rootfs, CapDrop: ALL, 128MB RAM 상한, 기본적으로 네트워크 비활성화입니다. 컨테이너는 응답 후 삭제됩니다. 호출 사이에 지속적인 공격 표면이 없습니다.
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 미들웨어
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
...
uap-migrate CLI는 모든 MCP 서버를 5분 이내에 서명된 UAP CapabilityCard로 감쌉니다. 멱등적입니다 — 이미 마이그레이션된 서버에서 다시 실행하면 아무 작업도 수행하지 않습니다.
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 서명된 페이로드의 일부입니다. 서명 후 어떤 필드를 변경해도 서명이 무효화되어 구조적으로 도구 중독을 차단합니다.
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 → 스쿼시 병합 → 브랜치 삭제. 로컬 개발에는 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
UAP · Universal Agent Protocol · 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개 연결 | ❌ N² 직접 HTTP | ⚠️ DNS 기반 | ❌ 없음 |
| 감사 추적 | ✅ 프로토콜 내장 추가 전용 로그 | ❌ 없음 | ❌ 없음 | ❌ 없음 |
| 분산 추적 | ✅ W3C TraceContext 필수 | ❌ 없음 | ❌ 없음 | ❌ 없음 |
| 스키마 검증 | ✅ 전송 계층의 AJV | ⚠️ 선택적 Zod | ❌ 없음 | ❌ 없음 |
| 토큰 수명 상한 | ✅ 15분 강제 | ❌ 강제 안 됨 | ❌ 강제 안 됨 | ❌ 해당 없음 |
| 배치 의미 | ✅ 직렬 · 병렬 · 트랜잭션 | ❌ 정의되지 않음 | ❌ 없음 | ⚠️ 모호함 |
| 바이너리 페이로드 | ✅ 네이티브 프레임 | ❌ Base64만 | ❌ Base64만 | ❌ Base64만 |
| IdP 모델 | ✅ 외부 IdP만 | ❌ 서버가 자체 OAuth 제공자 역할 | ⚠️ 제각각 | ❌ 없음 |
| 브리지 어댑터 | ✅ MCP + A2A 브리지가 2단계에서 제공됨 | ❌ 브리지 없음 | ❌ 브리지 없음 | ❌ 브리지 없음 |
| 수평 확장 | ✅ 무상태 · 모든 로드 밸런서 | ❌ 고정 세션 | ⚠️ 제각각 | ❌ 없음 |
| 변수 | 설명 | 기본값 |
|---|
KEYCLOAK_URL | Keycloak 기본 URL | http://localhost:8080 |
KEYCLOAK_REALM | Realm 이름 | uap |
UAP_AUDIENCE | JWT audience 클레임 | uap-gateway |
UAP_SIGNING_KEY_PATH | Ed25519 개인 키 hex 경로 | config/dev.privkey.hex |
UAP_DOCKER_SOCKET | Docker 소켓 경로 | /var/run/docker.sock |
UAP_AUDIT_LOG_DIR | 추가 전용 감사 NDJSON 로그 디렉터리 | /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 | 클라이언트 인증서 검증용 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 | 추가 전용 구조화 감사 로그 |
ulid | ^2.3 | 정렬 가능한 고유 ID |
@opentelemetry/sdk-node | ^0.52 | 분산 추적 |
ioredis | ^5.3 | 다중 인스턴스 서비스 레지스트리 |
@modelcontextprotocol/sdk | ^1.0 | MCP 브리지 어댑터 |
httpx (Python) | ^0.27 | Python SDK용 비동기 HTTP 클라이언트 |
pydantic (Python) | ^2.7 | Python 타입 검증 |