← Back to Blog

Auditing Serverless Functions for the 25 Posture Failures That Cost You Money or Get You Owned

Auditing Serverless Functions for the 25 Posture Failures That Cost You Money or Get You Owned

Two things people get wrong about serverless functions:

  1. 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.
  2. 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 loopwhile(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 sourceAKIA..., 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 / bodyconsole.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 requestconsole.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.mjs module 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/ratelimit with KV; runs before every matched route.
  • Fly.io — multi-machine deploys need Redis or LiteFS; fly-client-ip is 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.origin inside 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 detectioncf-ray AND x-nf-request-id / x-vercel-id indicates 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.4 or 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

Fact-check notes and sources

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.

← Back to Blog

Accessibility Options

Text Size
High Contrast
Reduce Motion
Reading Guide
Link Highlighting
Accessibility Statement

J.A. Watte is committed to ensuring digital accessibility for people with disabilities. This site conforms to WCAG 2.1 and 2.2 Level AA guidelines.

Measures Taken

  • Semantic HTML with proper heading hierarchy
  • ARIA labels and roles for interactive components
  • Color contrast ratios meeting WCAG AA (4.5:1)
  • Full keyboard navigation support
  • Skip navigation link
  • Visible focus indicators (3:1 contrast)
  • 44px minimum touch/click targets
  • Dark/light theme with system preference detection
  • Responsive design for all devices
  • Reduced motion support (CSS + toggle)
  • Text size customization (14px–20px)
  • Print stylesheet

Feedback

Contact: jwatte.com/contact

Full Accessibility StatementPrivacy Policy

Last updated: April 2026