Ship a public coding agent behind API Gateway and the internet will find it: credential stuffing on API keys, prompt floods that burn Bedrock tokens, WebSocket clients that open thousands of idle sessions, and tool-abuse loops that fill Redis with scratch keys. Usage plans alone are not enough. Pair API Gateway throttling with AWS WAF rate-based rules and Bot Control so abuse dies at the edge before it becomes a Lambda concurrency incident.
⚡ TL;DR: Put public agent HTTP/WebSocket APIs behind API Gateway usage plans (key + stage throttle) and WAF rate-based rules / Bot Control. Protect against prompt-flood and tool-abuse; combine with API Gateway WebSockets for multi-turn agents, AppConfig kill switches, and IAM condition keys. Edge limits first; app limits second.
Threat model for public agents
| Abuse | Symptom | Edge control |
|---|---|---|
| Prompt flood | Bedrock $ spike, EMF tokens skyrocket | WAF rate rule + usage plan burst |
| Tool-abuse loop | Redis/Dynamo write storm | Per-key throttle + app Cedar deny |
| WebSocket fan-out | Idle connections, fan-out cost | Route-level throttle + connection caps |
| Scrapers / bots | Noisy neighbor | WAF Bot Control |
| Key sharing | One key, many IPs | Usage plan quota + anomaly alerts |
// ❌ Public Function URL, no WAF, no usage plan
// Anyone who finds the URL can Invoke until account limits scream
✅ API Gateway (REST or HTTP API) + WAF WebACL + keys/JWT + app-level authz.
Usage plans and stage throttles
For REST APIs, usage plans attach API keys to throttle and quota. For HTTP APIs, use stage burst/rate limits plus JWT authorizers (keys are REST-centric).
// Infra sketch — throttle per key (CDK-ish pseudocode)
// UsagePlan: quota 10_000 requests/day, rate 20/s, burst 40
// Method /v1/agent/turn: API key required
// Method $connect (WebSocket): throttle 5/s per stage + custom authorizer
import {
APIGatewayClient,
CreateUsagePlanCommand,
CreateApiKeyCommand,
CreateUsagePlanKeyCommand,
} from "@aws-sdk/client-api-gateway";
const gw = new APIGatewayClient({});
export async function provisionTenantKey(tenantId: string) {
const key = await gw.send(
new CreateApiKeyCommand({
name: `agent-${tenantId}`,
enabled: true,
tags: { tenant: tenantId },
})
);
// attach to plan with rateLimit=20, burstLimit=40, quota=10000/DAY
await gw.send(
new CreateUsagePlanKeyCommand({
usagePlanId: process.env.USAGE_PLAN_ID!,
keyId: key.id!,
keyType: "API_KEY",
})
);
return key.value!;
}
Set stricter limits on expensive routes (POST /turn, POST /tool) than on health checks. For WebSockets, throttle $connect hard — reconnect storms are a classic outage.
WAF rate-based rules and Bot Control
Attach a regional WebACL to the API Gateway stage.
{
"Name": "agent-public-edge",
"Rules": [
{
"Name": "rate-limit-by-ip",
"Priority": 10,
"Action": { "Block": {} },
"Statement": {
"RateBasedStatement": {
"Limit": 200,
"AggregateKeyType": "IP",
"ScopeDownStatement": {
"ByteMatchStatement": {
"SearchString": "/v1/agent",
"FieldToMatch": { "UriPath": {} },
"PositionalConstraint": "STARTS_WITH",
"TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }]
}
}
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "agentRateByIp"
}
},
{
"Name": "bot-control-common",
"Priority": 20,
"OverrideAction": { "None": {} },
"Statement": {
"ManagedRuleGroupStatement": {
"VendorName": "AWS",
"Name": "AWSManagedRulesBotControlRuleSet",
"ManagedRuleGroupConfigs": [
{ "AWSManagedRulesBotControlRuleSet": { "InspectionLevel": "COMMON" } }
]
}
},
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "agentBotControl"
}
}
]
}
✅ Rate-based rules catch distributed floods that still use valid API keys from many IPs. Usage plans catch a single key going vertical. You want both.
❌ Relying only on Lambda reserved concurrency: abuse still costs you the WAF/API Gateway path and fills concurrency that legitimate tenants need. Kill switches in AppConfig help mid-incident but are not a rate limiter.
WebSocket-specific protections
From API Gateway WebSockets for multi-turn coding agents:
- Authorizer on
$connect(JWT / API key mapping) - Cap connections per tenant in DynamoDB (conditional put)
- Idle timeout + DynamoDB TTL session expiry hooks
- Per-message size limits; reject huge prompts at the gateway when possible
// ✅ Connection cap per tenant on $connect
import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
const ddb = new DynamoDBClient({});
export async function onConnect(event: {
requestContext: { connectionId: string; authorizer?: { tenantId?: string } };
}) {
const tenantId = event.requestContext.authorizer?.tenantId;
if (!tenantId) return { statusCode: 401, body: "unauthorized" };
try {
await ddb.send(
new PutItemCommand({
TableName: "agent-connections",
Item: {
pk: { S: `tenant#${tenantId}` },
sk: { S: `conn#${event.requestContext.connectionId}` },
ttl: { N: String(Math.floor(Date.now() / 1000) + 7200) },
},
// optional: maintain a counter item with ConditionExpression limit
})
);
} catch {
return { statusCode: 429, body: "too_many_connections" };
}
return { statusCode: 200, body: "ok" };
}
App-layer backstops
Edge rate limits will not understand “this tenant already spent $50 of Bedrock today.” Add:
- Per-tenant token budgets (EMF + Dynamo counter)
- Cedar/
IsAuthorizedfor tool abuse (Verified Permissions angle once live) - AppConfig kill of expensive tools under incident
- IAM tags so a runaway agent cannot write outside its prefix
Prompt-flood pattern: attacker sends max-size prompts in a loop. Mitigate with body size limits, WAF size constraints, and model-side max tokens — not only request rate.
Operational checklist
- [ ] API Gateway in front of all public agent HTTP/WebSocket endpoints (no raw Function URLs)
- [ ] Usage plan / stage throttle on expensive routes; strict
$connectlimits - [ ] WAF WebACL: rate-based by IP (scoped to
/v1/agent) + Bot Control - [ ] Connection caps per tenant; TTL cleanup for sockets
- [ ] Per-tenant Bedrock/token budgets as app backstop
- [ ] Alarms: 429 rates, WAF blocked requests, Bedrock spend anomalies
- [ ] Runbook: AppConfig kill switches + tighten WAF limit without redeploying Lambdas
Related reading
- API Gateway WebSockets for Multi-Turn Coding Agents
- AppConfig Feature Flags: Kill Switches for Agent Tools Without Redeploy
- IAM Condition Keys for Agent Runtimes: Limit Blast Radius by Tag
- CloudWatch EMF for LLM Cost Per-Tenant Token Metrics That Survive Sampling
Last updated on September 20, 2026
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- LLM context windows: Tokens, Context Windows, and Why Models Forget Mid-Task
- Zero-Copy Node Streams: Pipe Large S3 Objects Without Buffering
- SQL Joins Explained: INNER, LEFT, RIGHT, FULL, CROSS, and Self Joins
- PostgreSQL Performance Tuning: The Configuration Changes That Actually Matter
Newly added
- API Gateway + WAF: Rate-Limit Public Coding Agent Endpoints
- AWS Verified Permissions: Cedar Policies That Authorize Agent Tools
- ADOT OpenTelemetry: Trace Multi-Hop Agent Tool Calls Across Lambda
- ElastiCache Redis: Scratchpads and Tool-Result Cache for Multi-Turn Coding Agents
- Step Functions: Orchestrate Multi-Step Coding Agent Graphs Without Recursive Chaos
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.