Two things people get wrong about serverless functions:
- They are not "free." They run on a metered platform and a single abusive client can burn your monthly quota in fifteen minutes. The whole free-tier-shaped infrastructure economy assumes everyone gates their endpoints. Most don't.
- The platform does not protect them. A fresh Netlify Function, Cloudflare Worker, Vercel Serverless Function, or AWS Lambda has zero rate limit, zero Origin check, zero auth — the WAF in front (if any) protects against malformed bodies and known signatures, not abuse-shaped traffic that looks like real users.
The Serverless Posture Audit tool runs 25 static-lint checks against pasted handler code (any platform — it auto-detects the runtime from import patterns and global APIs) and a live HTTP probe against an endpoint URL. Both modes feed into a single fix prompt that emits platform-native code, not generic Express middleware.
The 25 checks, grouped by what they prevent
Quota / cost control (4)
No rate limit — the single most common posture failure. Anyone POST-ing to your function in a loop drains the free-tier minute counter or runs the metered-call counter into your billing tier. The fix is a per-IP Map for in-container limits or a KV / Redis counter for shared-state platforms (Cloudflare Durable Objects, Workers KV, Vercel KV, Upstash, Deno KV).
No outbound-fetch timeout — your handler fetch()s an upstream that hangs. The platform kills your container at 10–30 seconds, billed at full duration. Wrap every fetch call in AbortSignal.timeout(8000).
Unbounded loop — while(true) with no exit. Burns the entire execution budget and gets OOM-killed.
No input-size cap — a 50 MB JSON body is parsed, billed, and may crash the container. Reject with 413 once Content-Length exceeds a sane cap (100 KB for typical APIs).
Browser / CORS abuse (4)
No Origin / Referer allowlist — any URL on the internet can POST to your endpoint, including sites that cloned your code and now embed your backend for free. The fix is an explicit allowlist with a 403 on mismatch — at the very top of the handler.
CORS wildcard * — fine for public read endpoints, never for anything user-scoped. The browser will refuse the response if credentials are involved, but a misconfigured proxy can swap the Origin in and any site becomes able to read the user's data.
CORS wildcard with Allow-Credentials: true — browsers refuse this combination at runtime, but middleware that strips and rewrites the wildcard can break the safety. A critical-severity finding in the audit because the failure mode is silent and complete.
No HTTP-method allowlist — accepts any verb. Probes for surface area (TRACE, PATCH, OPTIONS) get cheap reconnaissance for free. Validate method at line 1 of the handler.
Secret + credential exposure (3)
Hardcoded secret in source — AKIA..., ghp_..., sk-..., Bearer ... patterns. Once committed, the secret is in git history forever. Move to platform env var, rotate the existing secret, install a pre-commit secret scanner.
Secret echoed in log / body — console.log(process.env.STRIPE_KEY) writes the key to your platform's log store, which is queryable by support staff and your future self. If you must log a token at all, log the first 4 chars + length.
Verbose log of full request — console.log(request) writes Authorization headers, cookies, IP, and body to logs. Log only what you need (path + method + status code).
Code-execution risks (3)
eval / new Function / vm.runIn — dynamic code execution. Anything user-controlled that reaches it becomes RCE. Replace with a JSON-schema interpreter or an explicit switch.
SSRF — unvalidated URL → fetch() — user-supplied URL passed to fetch() without validation. Attackers point it at 169.254.169.254 (cloud metadata service), file://, or your internal network. Parse the URL, reject non-HTTPS, reject RFC1918 / 169.254.0.0/16 / localhost, optionally allowlist by domain.
Path traversal risk — user input reaches readFile / createReadStream. ../../etc/passwd works unless you block it. Resolve to an absolute path inside a known root and reject anything outside.
Information disclosure (3)
Stack-trace leaked in response — returning error.stack reveals file paths, function names, library versions. Catch, log server-side with a correlation ID, return {error: "internal"} to the client.
Error handler returns raw Error object — non-serialized Error objects toString to [object Error] in some runtimes and leak internal fields in others. Always shape the response.
HTML response without CSP — any reflected input becomes an XSS vector without a Content-Security-Policy header.
Auth / replay (4)
Mutating endpoint with no HMAC / token verify — Origin-only checks are spoofable. Add a signed token (HMAC, JWT) tied to timestamp + path; reject stale tokens.
DELETE / PUT with no auth check — destructive verbs with only Origin gate are trivially replayable.
Admin path with no auth gate — /admin, /debug, /internal, /dev, /.env, /phpmyadmin patterns with no header check. If routing is the only gate, anyone who finds the URL gets in.
Math.random() for token / ID / nonce — V8's Math.random() is predictable enough to brute-force for security-sensitive values. Use crypto.randomUUID() or crypto.getRandomValues().
Caching + response hygiene (4)
No Cache-Control on private response — CDNs may cache user-scoped JSON. The next user sees the previous user's data. Always add Cache-Control: private, no-store to anything tied to a session, user, or auth token.
429 / 503 with no Retry-After — well-behaved clients use Retry-After to back off. Without it, they hammer you.
Missing Content-Type on response — browsers sniff and may mis-render HTML as text or vice versa. Always set content-type.
localStorage / sessionStorage in handler — these browser APIs don't exist in Workers / Lambda / Deno Deploy. Runtime error on first call. Use Workers KV / Deno KV / Redis / your DB instead.
Platform-native fix hints
Once the audit detects which platform your function targets, the fix prompt switches to platform idioms:
- Cloudflare Workers — Durable Objects or Workers KV for cross-isolate rate limit; Turnstile for human-gated endpoints.
- Netlify Functions — shared
_guard.mjsmodule imported by every function; Edge Functions for the upstream gate. - Vercel — Edge Middleware for centralized Origin + rate limit; Vercel KV / Upstash for shared state.
- Next.js Middleware —
@upstash/ratelimitwith KV; runs before every matched route. - Fly.io — multi-machine deploys need Redis or LiteFS;
fly-client-ipis trustworthy. - Deno Deploy — Deno KV for rate limit; signed cookies or Deno.jwt for per-user auth.
- AWS Lambda — API Gateway usage plans + WAF rules cover Origin + rate limit; validate
event.headers.origininside the handler too. - Render / Railway — no built-in WAF; put Cloudflare in front or implement in-app with Redis.
Live-probe mode
The probe tab sends a small number of GETs (baseline + cache-bust + OPTIONS preflight) through our proxy and from your browser. It does not burst, flood, or send malformed bodies — it's safe to run on production endpoints you own. It checks:
- Double-CDN detection —
cf-rayANDx-nf-request-id/x-vercel-idindicates layered CDNs, which complicates cache coherence and IP trust. - Cache leak — does the same URL with different Origin headers return the same cached body? If yes, your CDN is keying only on URL, not headers.
- Permissive CORS preflight — does an OPTIONS with a random Origin get
Access-Control-Allow-Origin: <random>echoed back? That's a wildcard in disguise. - Headers-leak surface — does the response include
Server: nginx/1.21.4or platform-specific request IDs that leak infrastructure detail?
When to run this
- Before publishing any new serverless function.
- After any change to a function's request handling (routes, auth, validation).
- Quarterly on every endpoint, even if nothing changed — the platform's defaults shift.
- Before integrating a third-party SDK that wraps
fetch(Stripe, Firebase, OpenAI) — your timeouts and Origin handling change.
Related tools
- Mega Security Analyzer — TLS, headers, and CSP at the site level (the layer above the function).
- DNS / Email Auth Audit — SPF / DKIM / DMARC / MX, separate audit surface.
- Security Headers Audit — header-only audit if you only want the surface check.
- WordPress Security Audit — WP-specific exposure surface (different platform, similar mindset).
Fact-check notes and sources
- AWS instance metadata service IP
169.254.169.254: AWS IMDSv2 documentation. - Cloudflare Durable Objects rate-limit pattern: Cloudflare Workers rate-limiting docs.
- Vercel Edge Middleware: Vercel Edge Middleware docs.
- AbortSignal.timeout availability across runtimes: MDN AbortSignal.timeout.
- Math.random() predictability: V8 issue 7790 — Math.random() PRNG.
- Web Crypto getRandomValues: MDN Crypto.getRandomValues().
- Netlify Edge Functions runtime (Deno): Netlify Edge Functions overview.
This post is informational, not security-consulting advice. Always test on staging before applying to production. Names of platforms (Cloudflare, Netlify, Vercel, AWS, Fly.io, Deno, Render, Railway, DigitalOcean) are nominative fair use.