2026-07-15· 9 min read· The Defen.so team

WAF vs CDN vs SDK: what actually stops SQL injection

Someone posts on Hacker News that they got hacked. In the comments there are always three suggestions and they never quite line up: "you needed a WAF," "you needed Cloudflare," "you needed proper input validation." All three suggestions are actually pointing at different layers of the stack.

This post walks through what each layer catches for the same attack, using a real SQL injection payload and real HTTP responses. Then it explains why you probably need at least two of them, and where they conflict.

The payload we're testing

A classic first-attempt SQLi payload against a search endpoint:

GET /search?q=1'+UNION+SELECT+*+FROM+users-- HTTP/1.1
Host: example.com
User-Agent: sqlmap/1.7

If the endpoint concatenates $_GET['q'] into a SQL query, this returns every row from the users table. The right defense catches this before it hits the database. The interesting question is which layer catches it, because each layer misses different attacks.

Layer 1: the CDN

Cloudflare, Fastly, CloudFront, these sit at the edge. Their job is speed. They cache your static assets, terminate TLS, absorb DDoS at L3/L4, and do some basic bot detection. On a plain CDN plan they will pass most application-layer attacks straight through to your origin because inspecting request bodies costs money and CPU they'd rather spend on caching.

Test the payload with Cloudflare Free enabled and no WAF rules:

$ curl 'https://example.com/search?q=1%27+UNION+SELECT+*+FROM+users--'
HTTP/2 200
server: cloudflare
{"users":[{"id":1,"email":"admin@example.com","password_hash":"$2y$10$..."}...]}

Cloudflare Free forwards it. The sqlmap/1.7 user agent gets flagged by the Bot Fight Mode heuristic, sometimes, but the request itself is not inspected. Even Cloudflare Pro's Managed Ruleset is off by default; you have to enable OWASP Core Rule Set explicitly, and it costs $20/mo/zone.

What the CDN catches: volumetric DDoS, some scraper bots, TLS downgrade attempts, and a tiny slice of L7 patterns via the "Bot Fight Mode" heuristic.

What it misses: essentially every OWASP Top 10 attack against your app.

Layer 2: the WAF

A Web Application Firewall is a rule engine that inspects HTTP request bodies looking for known-bad patterns. There are three deployment models, edge WAF (Cloudflare Pro, AWS WAF), reverse-proxy WAF (ModSecurity + nginx), and inline WAF (bundled with your app framework or SDK).

All three run essentially the same OWASP Core Rule Set: about 200 regex patterns that catch SQLi, XSS, path traversal, LDAP injection, command injection, and file inclusion. The payload above matches the 942100 rule ("SQL Injection Attack: SQL tautology detected"). Every mature WAF blocks it.

$ curl 'https://example.com/search?q=1%27+UNION+SELECT+*+FROM+users--'
HTTP/2 403
x-defenso-verdict: block
x-defenso-rule: sqli.union

{"error":"blocked","rule":"sqli.union"}

What the WAF catches: the entire OWASP Top 10 pattern surface, most tool-generated attacks (sqlmap, XSStrike, wpscan), and, if configured, bot user agents.

What it misses:

Layer 3: the SDK

An SDK, like @defenso/sdk-node or defenso/sdk-php, is a middleware that runs inside your app process. It has context the WAF doesn't have: who is the logged-in user, what's the current tenant, which routes exist, what does the DB row look like after this write. And it can enforce policies that need that context.

The payload above is caught by the SDK's rule engine (same OWASP patterns as the WAF), but the SDK also does things the edge WAF can't:

What the SDK catches: everything the WAF catches plus context-dependent attacks: rate abuse, auth bypass, business logic issues, data leaks, endpoint enumeration.

What it misses: volumetric DDoS (your process runs out of CPU before the SDK can decide anything) and true edge-only threats like TLS handshake attacks.

The stack you actually want

Put them in this order:

  1. CDN in front: absorbs DDoS + terminates TLS. Cloudflare Free or equivalent is enough for most sites.
  2. SDK in your app: catches OWASP patterns + everything context-dependent. This is the layer that stops the widest range of real attacks per dollar spent.
  3. Optional: WAF at the edge if you have compliance requirements that mandate it or if your origin is a black box you can't instrument.

The order matters: the CDN handles the noise so your SDK isn't wasted on scanner traffic, and the SDK handles the requests that got through with all the context needed to make smart decisions.

Where they conflict

Two things to watch for:

Client IP. When Cloudflare proxies to your origin, $request->ip() returns Cloudflare's IP, not the real client. The SDK's per-IP rate limits become per-Cloudflare-POP rate limits, which are useless. Fix: trust the CF-Connecting-IP header. Every SDK should read this by default. If yours doesn't, configure it.

Double 403. If Cloudflare blocks a request with its Managed Ruleset AND your SDK would also have blocked it, you'll see a Cloudflare-branded 403 instead of your app's. Not a real problem but confusing during debugging. Pick one layer for user-facing errors and disable that rule in the other.

How Defen.so fits

Defen.so ships the SDK layer (one line, fails open, ~4ms p50) plus optional edge deception at guard.defen.so. If you're already on Cloudflare Free, keep it, Defen.so complements it. If you're not on any CDN, put Cloudflare Free in front and Defen.so inside. That's the whole stack.

Try the SDK in a live sandbox at playground.defen.so, fire the payload above, watch it get blocked with a real x-defenso-verdict header. Or install with one command:

npx @defen.so/init

Free tier is 1 site forever. Every layer above is included.

Try Defen.so free