← Back to Logs

How Web Application Firewalls Actually Work

Try the interactive lab for this articleTake the quiz (6 questions · ~6 min)

Most people first hear about web application firewalls through a procurement sentence. It usually sounds neat: put a WAF in front of the site and common attacks get blocked automatically.

That sentence is not wrong. It is also too vague to be useful.

A web application firewall is not a magic shield and it is not a generic packet filter with marketing polish. A real WAF sits on the HTTP boundary, parses requests into structured components, normalises them into a canonical form, evaluates them against protocol rules and application attack heuristics, assigns some kind of confidence or score, then decides whether to allow, log, challenge, rate-limit, transform, or block before the origin application handles the request.

That sounds straightforward until you look at the details. Which parser does it trust? How much of the body does it inspect? Does it decompress gzip before matching? Does it decode %2f once or twice? Does it understand JSON, GraphQL, multipart uploads, and WebSocket upgrades? Does it block on the first signature or combine several weak signals into a score? Does it see the client IP directly or only another proxy's forwarded headers? Can it tell the difference between a code-tutorial forum post containing <script> as text and an actual reflected XSS attempt?

Those details decide whether the WAF protects the origin, whether it breaks legitimate traffic, and whether an attacker can route around it by abusing parser disagreement.

This article walks through the real mechanism. We will look at where the WAF sits in the request path, how it turns raw bytes into a model it can reason about, how rule engines and anomaly scores work, why request-body handling is expensive, where evasions come from, how tuning changes over time, and what a WAF can and cannot realistically stop.

A WAF Is An HTTP Parser And Policy Engine On The Application Boundary

A web application firewall is a control point that inspects web requests at the HTTP layer and applies security policy before the origin application executes the request.

That definition matters because it separates a WAF from three other controls people mix together:

Control Main observation layer Typical decision Typical purpose
Network firewall IP, port, protocol allow or deny connection network segmentation and exposure control
DDoS filter traffic volume and transport behaviour absorb, rate-limit, drop keep infrastructure alive under flood conditions
Reverse proxy HTTP routing and connection management route, buffer, retry, cache operational shaping of application traffic
WAF HTTP semantics and application attack patterns allow, log, challenge, block stop hostile or malformed application requests

In practice one product can do several of these jobs. A CDN edge can terminate TLS, cache static files, rate-limit abusive IPs, and run WAF rules in one fleet. An ingress proxy can route traffic and enforce OWASP Core Rule Set style signatures at the same point. The functions still differ. A SYN flood filter and an SQL injection rule are not the same mechanism just because the same vendor sells both.

The key point is that a WAF reasons about the request the application is about to process. It cares about things such as:

  • method
  • path
  • query parameters
  • header names and values
  • cookies
  • body shape and body fields
  • protocol anomalies such as invalid chunking or duplicated content length
  • signatures associated with common exploit families
  • sometimes client history, reputation, session context, or bot signals

A crude example helps. Suppose shop.example.eu receives this request:

POST /login HTTP/1.1
Host: shop.example.eu
Content-Type: application/x-www-form-urlencoded
Cookie: locale=el-GR
 
email=nikos%40example.eu&password=' OR 1=1 --

A network firewall might only see TCP port 443. A TLS terminator sees a valid HTTPS session. A reverse proxy sees a POST /login it can route upstream. The WAF is the layer that asks whether the password field now resembles an SQL injection payload aimed at a login flow.

That is why WAFs live on application boundaries where many requests share one origin surface. They are especially useful when you need one enforcement point in front of a monolith, a cluster of APIs, a CMS estate, or a set of legacy applications that cannot all be patched equally quickly.

They are also attractive when the team wants a mitigation window shorter than the application deployment cycle. If a dangerous exploit pattern starts hitting production at 09:12 and the backend code fix will not ship until 14:30, a WAF can often buy time by blocking or scoring a recognisable request shape at the edge.

That time-buying role is useful. It is also where teams start overclaiming. A WAF can reduce exploitability of classes of web attacks. It does not remove the bug from the application. If the vulnerable endpoint stays reachable by some alternate path, or if the exploit can be expressed in a shape the WAF does not model correctly, the bug remains live.

The First Real Job Is To Turn Raw Bytes Into A Canonical Request Model

Everything important starts with parsing and normalisation.

The attacker does not send an abstract idea like "SQL injection". They send bytes. Those bytes may be URL encoded, gzip compressed, chunked, spread across repeated parameters, hidden in JSON strings, or fragmented across headers and path segments. Before any rule can fire, the WAF has to decide what the request actually means.

A typical request pipeline looks like this:

TCP or QUIC session
  -> TLS termination
  -> HTTP framing parse
  -> header normalisation
  -> path normalisation
  -> query parse
  -> cookie parse
  -> body buffering / decompression / parse
  -> canonical request object
  -> rule evaluation
  -> enforcement action

That canonical request object is the WAF's world model. If the model is wrong, the decision will be wrong.

Consider four path variants that may or may not refer to the same origin resource:

/admin/login
/%61dmin/login
/%2561dmin/login
/admin/./login

A human can see that some of these might decode to the same route. A WAF has to define exactly when decoding happens, whether invalid encodings are tolerated, whether dot segments are collapsed, whether backslashes are normalised, whether UTF-8 is validated, and whether the origin will make the same choices.

This is why WAF documentation talks so much about transformations. Common ones include:

  • URL decode
  • HTML entity decode
  • Unicode normalisation
  • lowercase conversion for case-insensitive matches
  • path dot-segment collapse
  • compression decompression
  • JSON parser extraction
  • multipart boundary parsing
  • comment stripping in some rule languages

Suppose the raw URI contains a double-encoded traversal probe:

/download/%252e%252e%252fetc%252fpasswd

If the WAF decodes once, it sees %2e%2e%2fetc%2fpasswd. If the backend framework decodes twice, it may finally see ../etc/passwd. If those two components disagree about decode depth, the origin may interpret something more dangerous than the WAF inspected.

This is not an edge case. A large part of WAF evasion history comes from parser mismatch:

  • edge and origin disagree about duplicate headers
  • edge and origin disagree about chunked framing
  • edge and origin disagree about separator characters
  • edge and origin disagree about path decoding or UTF-8 validity
  • WAF inspects only the first JSON field with a duplicate key while the app uses the last
  • WAF gives up after a body-size limit while the app keeps parsing

Even ordinary header handling matters. Does the request have two Content-Length headers? One Content-Length plus Transfer-Encoding: chunked? Invalid chunk metadata? Obsolete folded headers? These are not just standards-compliance details. They are places where attackers try to create one interpretation for the WAF and another for the origin.

Well-run WAF deployments therefore spend significant effort on protocol strictness. Rejecting malformed ambiguity is often safer than trying to guess intent. If the request contains contradictory framing, silently accepting one branch can turn the WAF into the first half of a request-smuggling chain.

Canonicalisation also affects false positives. If a forum application in Dublin allows users to post code snippets, the literal string <script> may appear legitimately in the body. A WAF that only sees one decoded string without context may block useful traffic. A WAF that preserves context such as endpoint, content type, and parameter name has a better chance of distinguishing a tutorial post from a reflected XSS probe.

The rule engine comes later. The quality of the canonical request model decides whether the rest of the system has a chance.

Detection Is Not Just Regex On Strings

People often describe WAFs as if they scan for suspicious substrings. That is only part of the story.

Simple signatures do exist. A rule can certainly look for sequences like UNION SELECT, <script, ${jndi:, ../, or shell metacharacters in a dangerous parameter. But production WAFs usually evaluate a richer set of conditions:

  • where the value appeared
  • which transformations already ran
  • whether the match happened in the path, query, cookie, header, or body
  • which content type the request claims to carry
  • whether the HTTP method makes the shape surprising
  • how many separate indicators appeared in the same transaction
  • whether the target endpoint is known to allow rich text or free-form code

That means the rules are closer to a policy DSL than to a grep command.

A simplified ModSecurity style rule might look like this:

SecRule REQUEST_URI|ARGS|REQUEST_BODY "(?i:(?:union\s+select|or\s+1=1|sleep\s*\())" \
  "id:210100,phase:2,block,t:none,t:urlDecodeUni,msg:'SQLi pattern',tag:'attack-sqli',severity:'CRITICAL'"

The details vary by engine, but the structure is familiar:

  1. select request fields to inspect
  2. apply transformations
  3. run an operator or matcher
  4. attach metadata such as ID, phase, tags, and severity
  5. choose or contribute to an action

More advanced rules inspect parsed structures rather than raw strings.

Examples:

  • compare method and content type consistency
  • flag JSON keys that should never appear on a specific endpoint
  • reject a file upload if the claimed MIME type, filename extension, and magic bytes disagree
  • detect a GraphQL introspection query against an internet-facing production API where policy forbids it
  • enforce that X-Forwarded-For is absent from untrusted client traffic and added only by trusted proxies

Context matters because hostile syntax is often also valid business data in some places. Consider the string:

select * from users where id = ?

In a tutorial blog editor, that may be harmless text. In a login form parameter, it is weird enough to score. In an API endpoint that accepts SQL templates for a trusted analytics product, it may be expected. The same bytes are not equally suspicious everywhere.

This is why mature rulesets classify requests by surface area instead of by generic danger alone. They group paths into policy zones such as:

  • login and account recovery
  • checkout and payment attempts
  • rich-text editors
  • file upload endpoints
  • GraphQL API
  • JSON API with fixed schema
  • administrative panels

Then they adjust inspection rules accordingly.

The more the WAF understands about the endpoint contract, the fewer blunt false positives it needs to live with.

That does not mean WAFs become application firewalls in the full semantic sense. They still usually stop short of complete business-logic understanding. Most do not know that a Greek VAT number should match a specific customer account, or that a refund should never exceed the original purchase amount. That is application logic. WAFs are strong where attack intent is visible in request shape, protocol behaviour, or familiar exploit grammar.

This also explains why many modern WAF products mix several detectors:

  • static signatures for known exploit shapes
  • protocol compliance rules
  • reputation or bot signals
  • rate-based heuristics for repeated probes
  • endpoint learning or positive security models
  • virtual patch rules for newly disclosed CVEs

Calling all of that "string matching" hides the real design problem, which is how to combine weak and strong signals without drowning the site in false blocks.

Most Serious WAFs Separate Matching From Final Action Through Scoring

The simplest WAF policy says: if any critical signature matches, block.

That works for some cases. It also produces brittle operations.

Real traffic contains odd values. Security scanners used by your own team generate attack-like probes. Rich text editors contain HTML. Search endpoints contain punctuation. Mobile apps send legacy quirks that nobody documented properly. If one medium-confidence pattern means an immediate block, teams either accept painful false positives or weaken the rules until they stop being useful.

That is why many WAF deployments use anomaly scoring.

The model is straightforward:

  • each matched rule contributes points according to severity
  • protocol violations, attack signatures, and reputation signals all add to the running score
  • the transaction is blocked only if the score crosses a threshold
  • below the threshold the request might be logged, tagged, challenged, or allowed

A toy example:

rule: SQL keyword in login parameter           +5
rule: inline comment marker in parameter       +4
rule: invalid header folding                   +3
rule: high-risk IP reputation                  +2
rule: impossible user-agent / JA3 pairing      +2
threshold to block                            8
threshold to challenge                        6
threshold to log                              3

A benign request with one noisy pattern might score 3 and pass with logging. A real SQL injection attempt hitting a login endpoint with two classic patterns might score 9 and block immediately.

The scoring model buys two things.

First, it tolerates uncertainty. A single weak indicator does not have to carry the whole decision. Second, it makes tuning more surgical. Instead of deleting a whole rule because one endpoint triggers it too often, you can lower that rule's score on that endpoint or narrow the transformation chain that feeds it.

A representative decision flow looks like this:

request arrives
  -> normalise
  -> run protocol checks
  -> run signatures
  -> add bot / reputation / rate context
  -> compute total score
  -> if score >= hard block threshold: reject
  -> else if score >= challenge threshold: CAPTCHA or proof-of-work
  -> else if score >= monitor threshold: allow and log loudly
  -> else: allow

The exact actions depend on product and placement. A WAF in front of a consumer site may issue a browser challenge or JavaScript check. A WAF in front of a JSON API may return 403, 406, or a vendor-specific deny response because a challenge page would break clients. A purely internal reverse-proxy WAF may choose between allow, block, or shadow-log only.

This is one place where the difference between edge WAFs and in-cluster WAFs matters. Browser challenges are usable only for interactive clients. They are nonsense for a webhook receiver in Amsterdam or a machine-to-machine payments API in Helsinki.

Scoring also interacts with response codes. Many teams return 403 Forbidden for blocked requests. Some use 406 Not Acceptable, 429 Too Many Requests for behaviour-based throttling, or even connection resets at the edge. What matters operationally is consistency. Security tooling, dashboards, and incident responders need to know which outcomes correspond to WAF policy rather than application authorisation logic.

A good log line usually records at least:

{
  "ts": "2026-06-19T09:17:44Z",
  "host": "shop.example.eu",
  "method": "POST",
  "path": "/login",
  "client_ip": "203.0.113.44",
  "action": "block",
  "status": 403,
  "score": 11,
  "rule_ids": [210100, 920160, 949110],
  "tags": ["attack-sqli", "protocol-anomaly"],
  "request_id": "01J0WAFW7E6Q5M6V7R1K0G2X8A"
}

Without this evidence trail, the scoring system becomes impossible to tune responsibly.

Placement Decides What The WAF Can See And What It Can Break

The phrase "put a WAF in front of the app" hides a design choice. There are several reasonable places to put it, and each one changes visibility, trust boundaries, and failure modes.

A simple comparison table helps:

Placement Sees what Main upside Main limitation
CDN or cloud edge internet traffic before origin fastest mitigation, hides origin IP, absorbs global scanning limited app context, vendor feature constraints
Reverse proxy or ingress requests after edge, before app close to routing and origin semantics, easier internal telemetry still one layer removed from business logic
Sidecar or in-app library request inside service boundary more app context and identity data higher operational complexity, less central control
Agent on legacy host traffic to one application stack useful for old estates that cannot be refactored quickly coverage tied to host topology and patch state

Edge WAF

An edge WAF at a CDN or managed cloud boundary has strong economic advantages.

  • it blocks bad traffic before it reaches the origin network
  • it can hide the true origin IPs
  • it usually scales with the provider's global fleet
  • it gives one place to deploy emergency rules

That is why edge WAFs are common for public websites and APIs.

The tradeoff is context. The edge may not know which internal service will finally handle the request, which authenticated tenant the user belongs to, or which fields are expected for a very specific endpoint deep in the application.

Reverse proxy or ingress WAF

A WAF integrated into nginx, Envoy, HAProxy, or Kubernetes ingress sits closer to the origin contract. It can inspect host and route decisions after some internal routing context is known. It often has better access to internal telemetry and can log in the same request ID space as the app.

It also shares more fate with the application. If a bad ruleset breaks parsing or burns CPU on every request, it hurts the origin path directly.

In-app or sidecar WAF

Some products push WAF logic into a sidecar or application runtime hook. That can expose richer context such as authenticated identity, framework route metadata, or parsed body objects.

The cost is complexity. The more application-specific the enforcement point becomes, the less it behaves like one central protective barrier and the more it becomes another moving part that must be updated across many services.

Chained layers

Many mature systems use more than one layer.

For example:

client
  -> CDN edge WAF for broad internet filtering
  -> regional reverse proxy WAF for protocol strictness
  -> app-level validation for endpoint semantics

That layering is sensible as long as responsibilities stay clear.

  • the edge blocks obvious malicious patterns and shields origin capacity
  • the reverse proxy enforces HTTP correctness and local policy
  • the application validates actual business meaning

Problems start when teams expect one layer to provide guarantees it cannot prove. An edge WAF does not know the application's account model. An in-app validator does not hide the origin from scanners. A reverse proxy cannot infer that a transfer of 25,000 euros is fraudulent just because the JSON is well formed.

Placement also affects trusted headers. If the WAF is not the first HTTP-speaking hop, then it inherits assumptions from upstream proxies. A reverse-proxy WAF that trusts a spoofable X-Forwarded-For header is easy to mislead. A CDN WAF that sees only HTTP/2 at the edge but forwards HTTP/1.1 to origin must ensure the translation cannot create framing ambiguity downstream.

Where you place the WAF is therefore not just an operations choice. It defines the evidence available to the policy engine and the damage a bad rule can do.

Request Bodies Are Where Coverage Gets Expensive

Inspecting headers and paths is cheap compared with inspecting bodies.

Bodies are where many important attacks live:

  • SQL injection in form or JSON fields
  • stored XSS in rich-text submissions
  • template injection payloads
  • malicious file uploads
  • GraphQL queries and variables
  • XML entity abuse in old integrations
  • deserialisation probes in framework-specific formats

Bodies are also where WAF economics become awkward.

To inspect a body, the WAF may need to:

  1. buffer the request partially or fully
  2. decompress it
  3. parse its content type
  4. walk nested structures
  5. apply transformations to many fields
  6. keep memory and CPU use bounded under load

That creates several hard constraints.

Body size limits

Most WAFs cap how much of the body they inspect.

For example, a policy might say:

inspect first 1 MiB of JSON body
inspect first 256 KiB of form body
inspect multipart field names and metadata, but not full binary file contents
skip decompression above a configured ratio or size threshold

Those limits exist for good reasons. A WAF that fully inflates and deeply parses every 40 MiB upload can become a denial-of-service target. The attacker does not need to bypass detection if they can simply make inspection too expensive.

The downside is blind spots. If the dangerous payload sits after the inspected prefix, or inside a part type the WAF treats as opaque, the origin may see more than the WAF judged.

Streaming versus buffering

Some applications need low-latency streaming uploads or large request bodies. Full-body buffering adds delay and memory pressure. Some WAFs therefore inspect headers and early body chunks before deciding, or stream with partial inspection. That is a compromise, not a perfect answer.

The fundamental tension is simple: deep inspection wants complete context, while low-latency proxying wants to forward bytes as soon as possible.

Content-type awareness

A body parser that treats everything as one flat byte string misses useful context.

Consider these request types:

  • application/json
  • application/x-www-form-urlencoded
  • multipart/form-data
  • application/graphql-response+json on responses or GraphQL JSON requests upstream
  • XML payloads in older SOAP style integrations

Each needs different parsing logic.

A JSON body like:

{
  "search": "' OR 1=1 --",
  "sort": "price_desc",
  "filters": ["in-stock"]
}

is not just one blob of text. A decent WAF can tag that the suspicious pattern appeared in the search field, under a JSON parser, on an endpoint that expects a bounded string. That is stronger evidence than finding the same bytes somewhere in a large opaque stream.

File uploads

File uploads deserve special caution.

A WAF may inspect:

  • filename and extension
  • declared MIME type
  • magic bytes of the first kilobytes
  • archive nesting depth
  • image dimensions or structure for obvious bombs
  • macro presence in office documents in products that support this feature

But not every WAF is an antivirus engine, a sandbox, or a data-loss-prevention product. Teams sometimes configure a WAF as if it can fully adjudicate whether an uploaded document is safe. That is broader than the HTTP-boundary role many WAFs are built for.

The practical result is that body inspection is selective. It is one of the most valuable parts of a WAF, and also one of the first places where performance, latency, and false-negative risk collide.

Evasion Usually Means Making The WAF And The Origin Disagree

When people imagine bypassing a WAF, they often picture someone inventing a clever synonym for UNION SELECT. That happens. The bigger class of evasions is structural.

The attacker asks a different question: can I make the WAF parse request A while the application processes request B?

If the answer is yes, the WAF is inspecting the wrong transaction.

Common disagreement classes include the following.

Decode depth mismatch

The WAF decodes once. The app decodes twice. Or the WAF rejects invalid percent encodings that the app tolerates. Or the app normalises UTF-8 differently from the edge.

Header ambiguity

Which Content-Length wins? What happens if Transfer-Encoding and Content-Length both exist? Are duplicate headers concatenated, first one wins, or last one wins? If upstream and downstream differ, request smuggling becomes possible.

Path separator differences

One component treats backslash as a separator, another treats it as an ordinary character. One collapses //, another preserves it. One strips ; matrix parameters, another routes on them.

JSON and parameter collisions

Some parsers accept duplicate keys:

{ "role": "user", "role": "admin" }

If the WAF evaluates the first and the application uses the last, or vice versa, enforcement can drift. The same problem appears in query strings with repeated parameters.

Multipart weirdness

Multipart requests let attackers hide interesting values in boundary handling, filename encodings, nested parts, or oversized form fields. If the WAF inspects only metadata while the application unpacks inner content fully, coverage is partial.

Content-type confusion

Some frameworks accept JSON even when the header claims plain text, or they auto-deserialise data after custom middleware rewrites. If the WAF trusts the declared content type more than the origin does, it may choose the wrong parser.

Compression and nesting tricks

A WAF may limit decompression ratio or archive depth to protect itself. That is reasonable. It also means the origin may still unpack more structure than the WAF inspected if the pipeline is not aligned.

The operational lesson is blunt: a WAF works best when it is protocol-strict and as semantically close as possible to the origin's own parsing behaviour.

This is why teams should be wary of "be liberal in what you accept" thinking on internet-facing boundaries. Liberal acceptance feels user-friendly until two adjacent components are liberal in different ways. Then the attacker gets two parsers and one ambiguity channel.

It is also why WAF tuning should include negative testing for ambiguity, not only positive testing for obvious attack strings. Useful test cases include:

  • conflicting Content-Length and Transfer-Encoding
  • invalid chunk extensions
  • repeated query parameters
  • double-encoded path traversal strings
  • malformed UTF-8
  • duplicate JSON keys
  • large compressed request bodies
  • mixed newline styles in headers

A WAF that blocks clean textbook payloads but mishandles parser ambiguity is not mature protection. It is a demo.

Tuning A WAF Is An Ongoing Operational Discipline, Not A One-Time Install

This is the part vendors downplay and operators learn the hard way.

A WAF does not stay useful on defaults forever. Traffic changes. Endpoints change. Client libraries change. Attack campaigns change. New product features such as GraphQL, markdown editors, signed URLs, or large uploads create new normal traffic shapes that old rulesets misread.

A practical tuning loop has at least five stages.

1. Start in observation or low-disruption mode

For a new surface, many teams begin with logging or scoring thresholds set high enough to avoid immediate user impact. The goal is to learn what legitimate traffic looks like.

Questions to answer early:

  • which rules fire most on successful traffic?
  • are the hits concentrated on a few endpoints?
  • do mobile apps or partner integrations send odd but valid payloads?
  • are some content types unparsed because the policy is incomplete?

2. Group findings by endpoint and business function

A report that says "rule 941100 triggered 8,400 times" is not useful by itself. You need to know whether those hits were:

  • the public search box
  • a markdown editor
  • a partner webhook endpoint
  • the admin CMS used by five employees

Grouping by endpoint lets you decide whether to narrow a rule, change its score, add an exception, or fix the application surface.

3. Prefer narrow exclusions over global disablement

Teams under pressure often do this:

disable noisy XSS rule globally

That is understandable and usually wrong.

The better pattern is closer to:

for POST /admin/articles only,
ignore rule 941320 on body field article_html
because this endpoint intentionally stores rich HTML,
but keep the rule active elsewhere

Narrow exclusions preserve protection on the rest of the estate.

4. Feed rule outcomes back into engineering

A WAF alert stream is not just edge noise. It often points to application work.

Examples:

  • repeated SQLi probes on one endpoint may reveal that the route still exists publicly and should be retired
  • frequent false positives on free-form HTML fields may suggest a need for a dedicated sanitisation and content-policy design
  • strange header ambiguity probes may indicate the reverse-proxy chain needs stricter request framing enforcement
  • sustained bot pressure on login may mean rate limits and MFA flows need improvement

The strongest WAF programmes treat the WAF as instrumentation for the attack surface, not as a substitute for fixing it.

5. Rehearse emergency virtual patching

One real advantage of a WAF is speed. When a framework vulnerability drops publicly, the WAF can sometimes block the known exploit shape before the application patch is deployed.

That only works well if the team already knows how to:

  • identify the relevant request pattern
  • ship a temporary rule safely
  • monitor collateral damage
  • roll the rule back or narrow it after the app patch lands

If emergency rule deployment has never been rehearsed, the first live CVE window is a bad time to discover that the change path requires three approvals and a manual CDN propagation step nobody documented.

A mature WAF operation is therefore half security engineering and half traffic archaeology. You are constantly comparing intended request shapes with actual request shapes and deciding which differences matter.

A WAF Buys Real Protection Against Some Classes, And Very Little Against Others

A careful team should be explicit about where the WAF helps and where it is only marginal.

A WAF is often valuable against:

  • common reflected and stored XSS payload shapes in request input
  • familiar SQL injection and command injection probes
  • path traversal attempts visible in URL or body fields
  • protocol anomalies and malformed framing
  • scanner noise and exploit kits that reuse public payloads
  • rapid virtual patching for known request-pattern vulnerabilities
  • bot and credential-stuffing pressure when combined with rate controls and challenges

A WAF is much weaker against:

  • broken authorisation inside otherwise normal requests
  • business-logic fraud expressed through valid application flows
  • data abuse after successful authentication when the requests look legitimate
  • server-side request forgery that is triggered indirectly by a harmless-looking URL field the app later fetches internally
  • vulnerabilities hidden in encrypted inner payloads the WAF does not terminate or inspect
  • client-side attacks that do not depend on suspicious request syntax
  • compromised first-party clients sending perfectly valid API calls

This is why "we have a WAF" is not a serious answer to most product-security questions.

Suppose a customer support agent in Madrid can export every account's invoices because the backend forgot a tenant ownership check. The request may be a perfectly normal authenticated GET /api/invoices?tenant=.... Nothing about the HTTP shape tells the WAF that the caller should not see that data. The bug lives in authorisation logic.

Suppose a refund endpoint accepts any amount lower than 1,000 euros but forgets to compare it with the original order value. Again, the JSON can be completely ordinary. The WAF has no principled way to infer the business invariant.

Suppose an SSRF bug exists because the app downloads a user-supplied webhook target during verification. The initial request may contain a plain HTTPS URL. A WAF can sometimes block obviously suspicious targets such as link-local addresses or AWS metadata IPs if it understands the field semantics, but the reliable fix belongs in application validation and egress controls.

The healthy mental model is that a WAF is good at hostile syntax, dangerous protocol ambiguity, and known exploit grammar. It is not a universal interpreter of application intent.

That distinction also helps during incidents. If a suspected compromise used a normal browser session and valid workflow steps, spending three hours combing WAF signatures may tell you little. If the incident involved obvious exploit attempts against a known vulnerable endpoint, the WAF logs may be central evidence.

The Best WAF Deployments Are Integrated With The Rest Of The Edge, Not Isolated From It

A WAF on its own is rarely enough. The strongest deployments compose it with adjacent controls that reinforce the same boundary.

Common companions include:

  • strict reverse-proxy parsing and header sanitation
  • rate limiting on sensitive flows such as login and password reset
  • bot detection and browser challenges where interactive traffic exists
  • origin authentication so only trusted proxies can reach the app
  • structured request IDs that join WAF logs with application logs
  • response headers such as CSP and X-Content-Type-Options to reduce the impact of client-side bugs
  • application input validation and output encoding
  • rapid patch pipelines so virtual patches do not become permanent architecture

A concrete edge stack might look like this:

internet
  -> CDN edge: TLS termination, IP reputation, broad WAF rules
  -> regional proxy: strict HTTP parsing, route-specific WAF tuning, rate limiting
  -> application: authentication, authorisation, schema validation, business logic
  -> database and downstream services

Each layer reduces the burden on the next.

  • the CDN absorbs spray-and-pray probes and keeps noise away from origin
  • the regional proxy enforces canonical HTTP behaviour and tuned route policy
  • the app enforces identity, ownership, and business rules

This is also the right way to think about cost. Deep WAF inspection is not free. It consumes CPU, memory, latency budget, and operator time. If the application can cheaply reject impossible request shapes with tight schema validation, the WAF does not need to pretend to understand every business field. If the edge can stop obvious bad traffic early, the origin WAF can reserve deeper inspection for higher-value routes.

One practical design decision is whether to fail open or fail closed when the WAF layer itself degrades. Public consumer systems sometimes choose a controlled fail-open posture for low-risk paths to preserve availability, while keeping high-risk routes such as admin and auth behind stricter fail-closed policy. Internal regulated systems may make different choices. There is no universal answer. The choice depends on the value of the protected operation, the fallback controls in place, and the business cost of accidental outage.

That policy should be explicit long before a production incident. A half-broken WAF cluster is a bad moment to discover that nobody decided whether checkout traffic may bypass deep inspection.

A WAF Is Useful Because It Makes The HTTP Boundary Deliberate

The most accurate short description is not "a WAF blocks hackers". It is "a WAF makes the HTTP boundary an inspected, policy-driven boundary instead of a blind pass-through".

That matters because a huge amount of web risk is still visible in request shape.

Attackers probe paths, headers, bodies, encodings, and parser edge cases. They reuse old payload families because old payload families still work often enough. They count on the fact that public-facing applications are assembled from several components that do not always agree on what a request means.

A good WAF helps by enforcing one careful interpretation at the boundary, rejecting malformed ambiguity, scoring hostile syntax, and giving operators a place to ship temporary mitigations faster than an application release. It is strongest when its parser behaviour matches the origin closely, when its tuning is narrow and evidence-driven, and when the team treats its logs as feedback for fixing the application rather than as proof that the problem is solved elsewhere.

A bad WAF does the opposite. It runs generic signatures with little context, inspects only part of the request while pretending to see the whole thing, trusts spoofable upstream metadata, and accumulates so many false positives that operators quietly disable the useful rules.

The mechanism is not mystical. Parse carefully. Normalise consistently. Match in context. Score before you block when confidence is partial. Keep the WAF close to the origin semantics. Tune exceptions narrowly. Fix the application anyway.

That is how web application firewalls actually work.