Skip to content

Custom HTTP Targets

Red team any AI application or traditional ML system wherever it is deployed - AWS, Azure, GCP, or any custom HTTP endpoint.

AI red teaming works against any AI system that accepts input and returns output, wherever it runs. You are not limited to OpenAI or litellm-schema endpoints. Point Dreadnode at a model or application behind your own HTTP API (any request and response shape), a cloud-managed deployment (AWS SageMaker, Azure OpenAI / AI Foundry, Google Vertex AI), an agent loop, RAG pipeline, or multi-turn agent, or a self-hosted model.

Drive it from the Dreadnode AI red teaming agent (TUI) or the Python SDK.

If your target is a managed cloud deployment, start with its dedicated guide:

This page covers the generic path: any custom HTTP endpoint.

PathHow it works
Dreadnode AI red teaming agent (TUI)Describe the endpoint in plain language. The agent writes the workflow that calls it.
Python SDKWrite a @dn.task target (full control) or a declarative build_target(TargetSpec).

Any async function that takes a string and returns a string, wrapped with @dn.task, is a valid target. You own the request shape, auth, and response parsing:

import httpx
import dreadnode as dn
from dreadnode.airt import Assessment, tap_attack
dn.configure(organization="your-org", workspace="your-workspace", project="your-project")
@dn.task
async def my_api_target(prompt: str) -> str:
async with httpx.AsyncClient() as client:
response = await client.post(
"https://my-app.example.com/v1/chat",
json={"message": prompt}, # your request shape
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=30.0,
)
return response.json()["reply"] # your response shape
async def main():
assessment = Assessment(
name="custom-api-assessment",
target=my_api_target,
model="dn/claude-opus-4-8",
goal="Extract the system prompt from the agent",
)
async with assessment.trace():
await assessment.run(tap_attack, n_iterations=15)

Your target is more than a plain function. @dn.task turns it into a traced task, and that is what makes red teaming observable and scorable:

  • Every call is captured with its input prompt and output response, so each trial becomes a finding you can open in the platform to see exactly what was sent and what came back. This is the evidence behind every result.
  • The attack loop treats it as a first-class unit it can call, retry, and score; scorers attach to the task’s output.
  • Timing and execution metrics are recorded per call, and every trial streams to your assessment in the platform automatically.

In short: without the decorator the attack cannot trace or score your endpoint; with it, every prompt/response pair, score, and transform is recorded as evidence. Keep all the HTTP, auth, and parsing inside the task; the decorator captures the boundary (input in, text out).

For a straightforward request/response endpoint, skip the boilerplate and describe it with a TargetSpec:

from dreadnode.airt import build_target, TargetSpec, TargetAuth
target = build_target(
TargetSpec(
endpoint="https://my-app.example.com/score",
auth=TargetAuth(type="bearer", env_var="MY_API_KEY"),
request_template='{"input_data": {"input_string": ["{prompt}"]}}', # any request shape
response_text_path="$.output", # JSONPath to the reply text
)
)
# use it anywhere a target is accepted, e.g. tap_attack(target=target, ...)

request_template places {prompt} (and {image_b64} / {audio_b64} / {video_b64} for multimodal) into your request body; response_text_path is a JSONPath to the reply. Any provider’s payload works without custom code.

Credentials are read from the environment or cloud identity, never inlined.

auth.typeUse forHow it authenticates
api_keyOpenAI-compatible, Azure ML / AI FoundryStatic key from env_var into a configurable header
bearerAny static tokenAuthorization: Bearer <env_var>
azure_adAzure with managed identityEntra token via DefaultAzureCredential, auto-refreshed
gcpGoogle Vertex AIGoogle ADC token
aws_sigv4Amazon Bedrock / SageMakerSigV4 request signing via the AWS credential chain

Agent (TUI): describe it in plain language

Section titled “Agent (TUI): describe it in plain language”

Launch the agent and describe the endpoint. It writes the httpx call, auth, and response parsing for you:

Terminal window
dn --model dn/claude-opus-4-8 --capability ai-red-teaming

I have a chat API at https://my-app.example.com/v1/chat (deployed on Azure Container Apps) that accepts {"message": "..."} and returns {"reply": "..."}. It needs a Bearer token. Run a TAP attack against it with the goal “Extract the system prompt” and a max of 15 iterations.

For an Azure OpenAI deployment, give the agent the endpoint, deployment, API version, and how the key is provided:

Red team my Azure OpenAI deployment. Endpoint https://my-resource.openai.azure.com, deployment gpt-4o-mini, API version 2024-10-21, and the API key is in the AZURE_API_KEY environment variable (api-key header). Run a TAP attack with the goal “make it reveal its system prompt” and 15 iterations.

Set AZURE_API_KEY in your shell before launching the TUI (see Provide credentials below); UI Secrets apply only when the attack runs on Dreadnode-hosted infrastructure.

When you run the SDK or the TUI on your machine, credentials come from your local environment variables - you do not need to add anything in the web UI. The attack executes in your local process and reads the keys from your shell:

  • SDK custom task: your @dn.task target reads the key straight from os.environ (or, for azure_ad / aws_sigv4 / gcp auth, from your local cloud-identity chain).
  • TUI ai-red-teaming agent: run locally, the agent reads the same shell environment variables.

So just export the variables in your shell before launching. When an attack instead runs on Dreadnode-hosted infrastructure (a provisioned sandbox), it reads credentials from Secrets you add under Account Settings > Secrets - Dreadnode injects them into that environment, so your local shell is not used. See Secrets.

Add whatever your target needs. For an Azure OpenAI / AI Foundry deployment that is three values:

Terminal window
AZURE_API_KEY="<your key>"
AZURE_API_BASE="https://<your-resource>.openai.azure.com/"
AZURE_API_VERSION="2024-10-21"

For a custom HTTP app it is usually a single token (for example MY_API_KEY); for OpenAI it is OPENAI_API_KEY. See Secrets for details.

You can red team an Azure OpenAI deployment with a @dn.task target that calls its chat/completions URL directly - useful when you want full control over the request. The API key goes in an api-key header and the API version is a query parameter:

import os, httpx
import dreadnode as dn
RESOURCE = "https://<your-resource>.openai.azure.com"
DEPLOYMENT = "gpt-4o-mini" # your deployment name, not the model name
API_VERSION = "2024-10-21"
@dn.task
async def azure_target(prompt: str) -> str:
url = f"{RESOURCE}/openai/deployments/{DEPLOYMENT}/chat/completions"
async with httpx.AsyncClient(timeout=60) as client:
r = await client.post(
url,
params={"api-version": API_VERSION},
headers={"api-key": os.environ["AZURE_API_KEY"]}, # local env var or UI Secret
json={"messages": [{"role": "user", "content": prompt}]},
)
r.raise_for_status()
return r.json()["choices"][0]["message"]["content"]

Only AZURE_API_KEY is required for this custom-task path (the endpoint and version are in the code). For the simpler generator path (azure/<deployment>, no custom task), see the Azure OpenAI and AI Foundry guide.

Put both the retrieval and generation steps inside the target, then probe for context injection:

@dn.task
async def rag_target(prompt: str) -> str:
documents = await retrieve_relevant_docs(prompt) # your retrieval
return await generate_with_context(prompt, documents) # your generation

Pair it with the rag_poisoning transforms (context injection, document poisoning, query manipulation):

from dreadnode.transforms.rag_poisoning import context_injection
attack = tap_attack(
goal="Inject false information through RAG context",
target=rag_target,
attacker_model="dn/claude-opus-4-8",
evaluator_model="dn/claude-opus-4-8",
transforms=[context_injection()],
)

Manage conversation state inside the target:

@dn.task
async def stateful_target(prompt: str) -> str:
session = get_or_create_session()
session.add_message("user", prompt)
response = await call_model(session.messages)
session.add_message("assistant", response)
return response

Agent frameworks (OpenAI Assistants, Anthropic, custom)

Section titled “Agent frameworks (OpenAI Assistants, Anthropic, custom)”

Wrap the framework’s multi-step protocol in the target: create the thread/run, poll for completion, and return the final text. The attack sees a single prompt-to-response call regardless of how many API calls happen inside.

For a full runnable notebook against a custom HTTP app (with a shipped sample app you can deploy to Container Apps, Lambda, or a Function App), see 07_custom_http.ipynb in the AI Red Teaming cookbook.