Attack & Crawl Profile
Simulate Risk
Attack Scenario Preset
Crawl Intensity (Requests / Sec)
75 req/s
Crawl / Attack Duration
18 hours
SSR Function Compute Duration
240 ms
Time spent generating server-rendered HTML or fetching database rows.
Serverless Memory Allocated
1,024 MB (1.0 GB)
HTML / JSON Payload Size
85 KB / request
Edge Cache Hit Ratio
10% (90% SSR Misses)
Crawlers append random query parameters (`?_cb=123`) to force cache bypass.
CRITICAL FINANCIAL RISK: Runaway Serverless Bill Detected! 4.86M unthrottled requests will consume 403 GB of egress and 291 GB-Hours of Fluid Compute, triggering a surprise $1,428 overage invoice on Vercel Pro.
Vercel Surprise Invoice
$1,428
$1,408 over base $20 Pro plan
Cost on Cloudflare WAF
$25
Pro flat ($0 egress, WAF drops bots)
Total Bandwidth Generated
403 GB
Included 1 TB absorbed or exceeded
Serverless Fluid Compute
291 GB-Hrs
4,860,000 requests
Financial Exposure Comparison by Infrastructure Stack
UNMITIGATED SSR ATTACK
Line-Item Billing Ledger (Vercel Rates)
Simulated Audit
| Billing Dimension | Units Incurred | Included in Pro | Overage Rate | Line Item Cost |
|---|---|---|---|---|
| Fast Data Transfer (Egress) | 403.2 GB | 1,000 GB / mo | $40 per 100 GB ($0.40/GB) | $0.00 |
| Fluid Compute (Serverless GB-Hours) | 291.6 GB-Hrs | Included in tier | $40 per 100 GB-Hrs | $116.64 |
| Edge Middleware Invocations | 4.86 M reqs | 1 M free | $0.65 per 1 M reqs | $2.51 |
| Vercel Pro Base Subscription | 1 seat | Base Plan | $20 / month flat | $20.00 |
| TOTAL ESTIMATED VERCEL INVOICE | NET EXPOSURE: | $139.15 | ||
Zero-Bill-Shock Defense Playbook
Copyable Code
# Terraform Cloudflare WAF Rule: Block Aggressive AI Bots on Dynamic SSR Routes
resource "cloudflare_ruleset" "block_ai_crawlers_ssr" {
zone_id = var.cloudflare_zone_id
name = "Block AI Crawlers on Dynamic Next.js SSR"
description = "Drops known scraping bots before reaching Vercel origin serverless lambdas"
kind = "zone"
phase = "http_request_firewall_custom"
rules {
action = "block"
expression = join(" and ", [
"(http.request.uri.path contains "/api/" or http.request.uri.path contains "/products/")",
"(cf.client.bot or http.user_agent contains "Bytespider" or http.user_agent contains "ClaudeBot" or http.user_agent contains "Meta-ExternalAgent")"
])
description = "Immediate 403 on dynamic routes for scraping bots"
enabled = true
}
}
// middleware.ts - Sliding Window Rate Limiting using Upstash Redis on Vercel Edge
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(20, '10 s'), // Max 20 reqs per 10 seconds per IP
analytics: true,
prefix: 'vercel_ddos_protect',
});
export async function middleware(request: NextRequest) {
const ip = request.ip ?? '127.0.0.1';
const userAgent = request.headers.get('user-agent') || '';
// Block known aggressive scrapers instantly
if (/Bytespider|ClaudeBot|Meta-ExternalAgent/i.test(userAgent)) {
return new NextResponse('Crawler Access Denied', { status: 403 });
}
// Rate limit dynamic SSR and API routes
if (request.nextUrl.pathname.startsWith('/api') || request.nextUrl.searchParams.toString()) {
const { success, limit, remaining, reset } = await ratelimit.limit(ip);
if (!success) {
return new NextResponse('Too Many Requests (Rate Limit Exceeded)', {
status: 429,
headers: {
'Retry-After': Math.ceil((reset - Date.now()) / 1000).toString(),
},
});
}
}
return NextResponse.next();
}
export const config = {
matcher: ['/api/:path*', '/dashboard/:path*', '/search/:path*'],
};
// vercel.json - Native Vercel Firewall Rate Limiting Rules
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"crons": [],
"security": {
"attackMode": {
"enabled": false
},
"firewall": {
"rules": [
{
"name": "Throttle Dynamic Search Queries",
"action": "rate_limit",
"rateLimit": {
"algo": "fixed_window",
"window": "1m",
"limit": 60,
"keys": ["ip"]
},
"condition": {
"path": "/search"
}
},
{
"name": "Deny Bad Bots",
"action": "deny",
"condition": {
"userAgent": "(?i)(bytespider|metabot|claudebot)"
}
}
]
}
}
}
# audit_vercel_logs.py - Identify IPs burning serverless Fluid Compute
import sys
import json
from collections import Counter
def analyze_vercel_drain(log_file):
ip_counter = Counter()
ua_counter = Counter()
total_compute_ms = 0
with open(log_file, 'r', encoding='utf-8') as f:
for line in f:
try:
entry = json.loads(line)
ip = entry.get('proxy', {}).get('clientIp', 'unknown')
ua = entry.get('proxy', {}).get('userAgent', 'unknown')
duration = entry.get('lambda', {}).get('executionDurationMs', 0)
ip_counter[ip] += 1
ua_counter[ua] += 1
total_compute_ms += duration
except Exception:
continue
print("=== TOP 5 SUSPICIOUS BOT IPS ===")
for ip, count in ip_counter.most_common(5):
print(f"IP: {ip:<18} Hits: {count}")
print("
=== TOP 5 USER AGENTS ===")
for ua, count in ua_counter.most_common(5):
print(f"UA: {ua[:40]:<42} Hits: {count}")
print(f"
Total Serverless Execution Drain: {total_compute_ms / 1000 / 3600:.2f} CPU Hours")
if __name__ == '__main__':
if len(sys.argv) > 1:
analyze_vercel_drain(sys.argv[1])
else:
print("Usage: python audit_vercel_logs.py vercel_export.ndjson")