9 views
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 |
typescript
✅ API Gateway (REST or HTTP API) + WAF WebACL + keys/JWT + app-level authz.
// ❌ Public Function URL, no WAF, no usage plan
// Anyone who finds the URL can Invoke until account limits scream
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).typescript
Set stricter limits on expensive routes (// 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!;
}
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.json
✅ 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.
{
"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"
}
}
]
}
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
typescript
// ✅ 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
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
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- REST API Design Best Practices: The Patterns That Make APIs a Joy to Use
Newly added
- n8n vs AI Agent: I Gave Both the Same Job (Local, Ollama)
- Local AI Video Generator: Build Your Own App (Free, Python, No GPU)
- AI Wrote Raw Binary Instead of Code. Here’s What Happened (WebAssembly)
- I Built a Browser That Only Runs Encrypted Binary (Ep 1)
- RAG From Scratch: Chunking, Embeddings, Reranking and Evaluation (Real Numbers)
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.