Skip to main content

Red-Team Adapter — Overview

This is the primary purpose of the whole project. Clippy Chat is a realistic target so that this adapter has something real to drive.

Palo Alto AI Red Teaming can attack many targets out of the box (hosted model APIs, plain OpenAI-compatible endpoints, and so on). But real applications rarely expose a naked model: they sit behind authentication, use a custom request/response shape, and often stream their answers. To point the platform at one of those, you write a custom target adapter — a small Python module that teaches the platform how to talk to your endpoint.

Clippy Chat is deliberately awkward in exactly the ways a real app is:

Real-world wrinkleHow Clippy Chat has itWhat the adapter must do
Auth is requiredEvery /api/chat call needs a Keycloak machine (M2M) bearer JWT whose scope includes clippy-api.Fetch a token via the client_credentials grant, cache it, attach it as Authorization: Bearer ….
Custom request body{"conversationId": <uuid>, "message": <str>}, not the OpenAI messages array.Build that body, minting a fresh conversationId per probe.
Streaming responseSuccess is text/event-stream (SSE), not JSON.Concatenate the delta frames into one reply string.
Distinct error channelErrors come back as a JSON envelope {"error": "..."} with a non-2xx status.Detect non-2xx, surface the error instead of treating it as a model answer.

The adapter contract​

The adapter implements three functions the platform calls in order. Each maps cleanly onto one concern:

authenticate(context) pre_process(context, input) post_process(context, resp)
one OAuth token call ─▶ build POST /api/chat ─▶ parse SSE → reply text
cached as context.auth (fresh UUID per probe) (or surface an error)
│ │ ▲
└── token ──────────────────────┴──────────────▶ Clippy Chat ───────┘
(SSE or JSON error)
  • authenticate(context) — one OAuth client_credentials call to the token endpoint. Returns an AuthResult with the access token and a TTL, so the platform caches it and refreshes shortly before expiry.
  • pre_process(context, inference_input) — turns one red-team prompt into a POST /api/chat request: the bearer header plus a JSON body with a new UUID so every probe is an independent single-turn conversation.
  • post_process(context, raw_response) — turns Clippy's response into the reply text the engine scores: handle rate-limits, surface config/auth errors loudly, then parse the SSE stream.

How it maps to the app​

Adapter stepClippy Chat sourceContract
authenticateKeycloak (external OIDC) → src/lib/auth/bearer.ts verifies the tokenToken scope must contain clippy-api (M2M_SCOPE) or verifyBearer rejects it. Once M2M_AUDIENCE is armed, aud must contain it too — the runner's Keycloak client needs an audience mapper before that flag flips (k8s/m2m-audience-rollout.md)
pre_processsrc/routes/api/chat.ts → src/lib/chat/service.ts (ensureConversation)Body {conversationId, message}; a fresh UUID auto-creates the conversation
post_processsse() helper in src/routes/api/chat.tsevent: delta → reply tokens; event: done → ignore; event: error → mid-stream failure (still HTTP 200)

The adapter catalog​

The repo ships three custom adapters. They fall into two families by what they attack — the model (through the chat app) or the tools (through the MCP server directly). Pick by target:

AdapterAttacksEndpointAuthTransportUse when
clippy_redteam_adapter.pyThe modelPOST /api/chatKeycloak M2M bearerSSE streamDefault. Jailbreak / harmful-content probes against Clippy the chatbot.
clippy_redteam_debug_oauth2.pyThe modelPOST /api/chatKeycloak M2M bearerSSE streamSame as above, but auth is failing and you need a full OAuth2 trace.
clippy_redteam_mcp.pyThe toolsPOST …/mcp (JSON-RPC)Keycloak clippy-mcp-client bearerJSON-RPC / SSEFuzz a tool's inputs directly — web_search, get_daily_news, or the high-blast-radius scm_config.

The two chat adapters share one request/response contract (the sections below describe it); they differ only in identity tracing. The MCP adapter is a different shape entirely — it injects the prompt into a tool argument and bypasses the model.

:::note Placeholders only The endpoints below (chat.example.com, auth.example.com, realm myrealm) are examples. Swap in your own target's values. :::