Skip to main content

Reduce false positives and improve AI application availability with optimized Azure Front Door WAF policies

Diagnostic PlaybookFront Door WAFDRS ExclusionsCustom RulesSQLI False Positives

Fixing Legitimate AI Drops: Tuning Azure Front Door
WAF Policies Using Rule Exceptions

A well-tuned WAF and an LLM system prompt want the same thing from opposite directions. The WAF is trained to treat quotes, semicolons, comment sequences, and the word SELECT as evidence of an attack. Your prompt engineering team just shipped a system prompt that contains all four, legitimately, because it's teaching the model to write SQL. Both sides are doing their job correctly. The fix isn't turning the WAF down — it's teaching it exactly where to stop looking.

The failure signature this guide resolves
# A real Azure Front Door WAF log entry, shape preserved, from a blocked
# request carrying an LLM system prompt in the JSON body:

{
  "resourceId": "/SUBSCRIPTIONS/.../FRONTDOORWEBAPPLICATIONFIREWALLPOLICIES/PROD-WAF",
  "operationName": "Microsoft.Network/FrontDoorWebApplicationFirewallPolicies/Match",
  "properties": {
    "clientIP":          "10.20.0.14",
    "requestUri":         "https://api.contoso.com/v1/chat/completions",
    "ruleName":            "Microsoft_DefaultRuleSet-2.1-SQLI-942130",
    "policy":              "PROD-WAF",
    "action":              "Block",
    "policyMode":          "Prevention",
    "matchVariableName":  "RequestBodyJsonArgValue:messages[0].content",
    "matchVariableValue": "...Given a users table with columns id, email,
                            created_at -- write a query that returns...",
    "details": {
      "matchedDataType": "SQL_COMMENT_SEQUENCE",
      "anomalyScore":     5
    }
  }
}

# The system prompt is 100% legitimate: it's teaching the model to write
# SQL. The WAF sees the SQL comment token "--" inside a JSON string and
# scores it exactly like it would score a real injection attempt, because
# structurally, to a regex, it IS the same four characters.

# A second, related signature — this time a code-review endpoint:
{
  "ruleName":            "Microsoft_DefaultRuleSet-2.1-SQLI-942150",
  "matchVariableName":  "RequestBodyJsonArgValue:code_diff",
  "matchVariableValue": "DELETE FROM cache WHERE expires_at < NOW();",
  "details": { "matchedDataType": "SQL_INJECTION_LOGIC", "anomalyScore": 5 }
}
# A legitimate code diff, submitted to an AI code-review tool, blocked
# because it CONTAINS a SQL statement as literal text — which is the
# entire point of the endpoint.

Symptom: Legitimate requests to an LLM-backed endpoint return 403 Forbidden from Front Door, specifically when the payload contains system prompts about SQL, code samples, or structured data with quotes and semicolons.  Failure point: Managed WAF rules in the SQLI rule group pattern-match on token sequences (--, '; DROP, 1=1, keyword clusters) that appear identically in legitimate AI payloads and real attacks — the WAF has no way to distinguish intent from shape.  Default platform behaviour: The Default Rule Set (DRS) inspects the full request body, including JSON fields, and scores every match toward a single anomaly threshold. It does not know your endpoint is an LLM API and was never told to treat it differently.

Score ≥ 5
The DRS 2.0+ anomaly threshold. One Critical match alone reaches it; three Warning matches (3 each) can too
3 scopes
Exclusions apply at rule set, rule group, or single-rule level. Single-rule is the most granular and the one to prefer
InitialBodyContents
The one place you cannot create an exclusion — the raw body evaluated before JSON/POST parsing
Custom rules first
Custom rules always evaluate before the DRS. A scoped Allow rule can bypass managed-rule inspection entirely for one URI

The Default Rule Set that ships with Azure Front Door's WAF was written to catch SQL injection the way it has looked for over a decade: quotes and semicolons chained together, comment sequences that truncate a query, boolean tautologies like 1=1, keyword clusters like UNION SELECT. Every one of those patterns also appears, entirely legitimately, in the payloads modern AI applications send over HTTP — a system prompt instructing a model how to write SQL, a code-review tool submitting a diff that happens to contain a real SQL statement as literal text, a documentation assistant quoting a query from a knowledge base. The WAF cannot tell the difference between an attacker typing 1=1 to break out of a query and a system prompt containing the string "explain why 1=1 is always true" as a teaching example — because at the level the WAF operates, there is no difference. Fixing this is not about weakening the WAF. It's about telling it, with surgical precision, exactly which field on exactly which endpoint is allowed to contain that shape of text — and leaving everything else exactly as suspicious as it was.

Figure 1 — How a legitimate AI payload accumulates enough anomaly score to block
DRS 2.0+ ANOMALY SCORING — a single request, multiple rule matches, one thresholdPOST /v1/chat/completionsJSON body:system prompt about SQL942150 matches "--" → Warning (+3)942130 matches "1=1" → Critical (+5)ANOMALY SCORE = 8threshold is 5 —one Critical match ALONE clears itPREVENTION MODE → 403 BLOCKEDThe scoring is additive across the WHOLE request.Even if you fix rule 942130, the "--" match on 942150alone won't clear 5 — but a second harmless-lookingmatch elsewhere in the SAME body will.THE FIX targets the FIELD, not the request or the whole rule.Exclude "messages[*].content" from rules 942150 and 942130 — the WAF stops scoring THAT fieldfor those two rules only. Every other field, every other rule, and every other endpoint keepsfull inspection. The attack surface removed is exactly one field on one rule — nothing broader.
Anomaly scoring is additive across the entire request body, not per-field. A payload can accumulate enough score to block from two or three individually-minor matches, which is why fixing "the one obvious rule" sometimes doesn't clear the block — another quieter match elsewhere in the same JSON body is still contributing. Targeted field-level exclusions solve this at the source rather than chasing each contributing rule individually.
01How Anomaly Scoring Actually Decides to BlockRoot Cause

Understanding the fix requires understanding the scoring model first, because it's not what most people assume. DRS versions 2.0 and later don't block on the first rule match. They accumulate an anomaly score across every rule that matches anywhere in the request, and only act once that score crosses a threshold.

SeverityScore contributionBlocks alone in Prevention mode?
Critical5Yes — a single Critical match reaches the threshold by itself
Error4No — needs one more match of any severity to cross 5
Warning3No — needs another match; two Warnings together clear it
Notice2No — several Notices, or paired with a higher severity, needed

The threshold itself is fixed at 5. In Prevention mode with the anomaly score action set to Block, any request that accumulates 5 or more is blocked — full stop, regardless of which specific rules contributed. This has a direct, practical consequence for AI payloads: fixing the one rule you can see in the log might not clear the block, because a second, quieter match elsewhere in the same JSON body is still adding to the same score. A system prompt that mentions SQL comment syntax and a boolean tautology example in the same message will trigger two separate rules, and disabling only one leaves the other still contributing toward 5.

Detection mode vs Prevention mode changes what "blocked" means

In Detection mode, a request that crosses the anomaly threshold is logged but allowed through — useful for understanding what would break before you commit to blocking it. In Prevention mode, the same request is actually rejected. If you're chasing down false positives on a policy already in Prevention, it is worth temporarily testing the same payload in a Detection-mode policy (or a scoped custom rule) to see the full set of rules it would have triggered, not just the first one that happened to appear in the block log.

02Why LLM Payloads Specifically Trigger SQLI RulesConcept

The SQLI rule group in the Default Rule Set is pattern-matching on token shapes that are genuinely rare in ordinary web traffic — a normal contact form or product search rarely contains the string 1=1 or a SQL comment marker. AI-adjacent endpoints break that assumption completely, and for reasons specific to what they actually do.

AI payload patternWhat triggersWhy it's legitimate
System prompts teaching SQLSQL comment sequences (--, /* */), tautologies (1=1)A text-to-SQL, data-analyst, or DBA-assistant system prompt necessarily contains SQL syntax as instructional content
Code review / diff analysis endpointsFull SQL statements as literal strings (DELETE FROM, UNION SELECT)The endpoint's entire purpose is to receive and analyze real code, which may legitimately include SQL
RAG grounding data with quotes/semicolonsChained special characters resembling injection syntaxRetrieved document chunks — logs, config snippets, structured data — carry the same punctuation an attacker would use
Long, information-dense JSON bodiesMultiple minor matches accumulating anomaly scoreA single large payload has more surface area for any individual token pattern to appear somewhere in it, purely by volume
JSON body inspection is a DRS 2.0+ feature — know your version

If your WAF policy is still on an older DRS version, it may not parse JSON request bodies into structured arguments at all, in which case the false positives you're seeing come from a cruder, whole-body inspection with fewer tuning options available. Confirm your ruleset version first (Microsoft_DefaultRuleSet, version 2.0 or later) — the field-level exclusion techniques in this article depend on the WAF actually parsing messages[0].content as a named JSON argument rather than treating the whole body as one undifferentiated blob.

03The Exclusion Gap: What You Cannot ExcludeCorrection

Before building any exclusion, know its limits — because this is where teams burn hours convinced their exclusion is broken, when actually they've hit a documented gap. Some managed rules evaluate the raw payload of the request body before it's parsed into POST arguments or JSON arguments. When that happens, WAF logs show a matchVariableName of InitialBodyContents or DecodedInitialBodyContents — and Microsoft's documentation is explicit: you cannot currently create exclusions for initial body contents.

Practically, this means: you create a clean, correctly-scoped exclusion for RequestBodyJsonArgValue:messages[0].content, deploy it, and the block persists — because the rule that's actually firing matched on the raw body before the JSON parser ever separated out that field. The log will tell you which situation you're in if you read it carefully.

matchVariableName in logCan you exclude it?What to do instead
RequestBodyJsonArgValue:fieldYes — standard field-level exclusionExclude that named field from the specific rule (Section 6)
RequestHeaderNames / ValuesYesExclude the specific header name or value pattern
PostParamValue:fieldYesExclude the named POST argument
InitialBodyContentsNo — not currently supportedUse a custom Allow rule scoped by URI (Section 7), or disable the specific rule for that endpoint via a scoped custom rule
DecodedInitialBodyContentsNo — not currently supportedSame as above
Field-name-only exclusions have a subtler gap too

When matchVariableName shows as CookieName, HeaderName, PostParamName, or QueryParamName (rather than ...Value), it means the name of the field triggered the rule — not its contents. Azure's own documentation notes you currently can't create exclusions for cookie names, header names, POST parameter names, or query parameter names in this situation. If your log shows this pattern, the field itself needs renaming, or you fall back to a custom rule.

Architectural Topology: Failing vs Remediated

LayerFailing configuration (current)Remediated configuration (fix)
Scope of fixWhole SQLI rule group disabled globallySingle rule, single named field, single endpoint
Exclusion granularityNone — or applied at rule-set level (too broad)Rule-level exclusion on RequestBodyJsonArgValue:messages[*].content
Endpoints affectedEvery endpoint behind the WAF policy loses SQLI coverageOnly the specific LLM/code endpoint via URI-scoped custom rule
Raw-body matchesIgnored or worked around by disabling the whole ruleIdentified via log inspection; routed to custom Allow rule instead of a (non-existent) exclusion
RolloutBig-bang switch to Prevention with new exclusionsDetection mode validation window before Prevention
Custom rule priorityNot used — relying entirely on managed-rule tuningScoped Allow rule evaluated before DRS, bypassing inspection for one exact match
Non-AI endpointsSame reduced protection as the AI endpoint (collateral)Untouched — full SQLI coverage remains everywhere else
Review cadenceExclusion set once, never revisitedMonthly log review; exclusions re-validated after DRS version bumps
05Fix 1 — Find the Exact Rule and Field from the LogsDiagnosis

Every exclusion starts from a log entry, not a guess. Pull the WAF logs for the blocked request and read three fields: which rule fired, what matchVariableName it matched on, and what value triggered it. Those three answer exactly what to exclude and how.

KQL — find recent WAF blocks on your LLM/AI endpoints// Requires diagnostic settings on the Front Door WAF policy -> Log Analytics. AzureDiagnostics | where ResourceProvider == "MICROSOFT.NETWORK" | where Category == "FrontdoorWebApplicationFirewallLog" | where action_s == "Block" | where requestUri_s has_any ("/v1/chat/completions", "/v1/code-review", "/api/ai/") | project TimeGenerated, requestUri_s, ruleName_s, matchVariableName_s, details_matches_s, policyMode_s | order by TimeGenerated desc | take 50 // The two columns that matter most: // ruleName_s -> e.g. "Microsoft_DefaultRuleSet-2.1-SQLI-942130" // matchVariableName_s -> e.g. "RequestBodyJsonArgValue:messages[0].content"
Azure CLI — pull the same detail for a single known request# If you have a specific blocked request's timestamp/client IP from a # support ticket, filter directly rather than scanning a broad window. az monitor log-analytics query \ --workspace $WORKSPACE_ID \ --analytics-query " AzureDiagnostics | where TimeGenerated between (datetime(2026-07-10T14:00:00Z) .. datetime(2026-07-10T14:10:00Z)) | where Category == 'FrontdoorWebApplicationFirewallLog' | where clientIP_s == '203.0.113.42' | project TimeGenerated, ruleName_s, matchVariableName_s, details_matches_s " -o table
Read matchVariableName as a recipe, not just a diagnostic

The format of matchVariableName tells you exactly which exclusion match-variable type to use. RequestBodyJsonArgValue:messages[0].content maps to an exclusion with match variable Request body JSON args and a selector of messages[0].content (or a wildcard pattern, depending on your JSON structure). RequestHeaderNames maps to a header-name exclusion. PostParamValue:comment maps to a POST-argument exclusion. There is no translation step — the log is telling you the exact selector to type into the exclusion.

06Fix 2 — Build a Narrow, Single-Rule ExclusionThe Fix

This is the primary tool, and it should be your first attempt for any match that isn't InitialBodyContents. Scope it as tightly as the log allows: a single rule ID, a single named field, nothing broader. Two things stay true after this fix — every other field in the request is still inspected by this rule, and this field is still inspected by every other rule.

Bicep — exclude the system-prompt field from the two rules it triggersresource wafPolicy 'Microsoft.Network/frontDoorWebApplicationFirewallPolicies@2022-05-01' = { name: 'PROD-WAF' location: 'Global' sku: { name: 'Premium_AzureFrontDoor' } properties: { policySettings: { enabledState: 'Enabled' mode: 'Prevention' } managedRules: { managedRuleSets: [ { ruleSetType: 'Microsoft_DefaultRuleSet' ruleSetVersion: '2.1' ruleSetAction: 'Block' ruleGroupOverrides: [ { ruleGroupName: 'SQLI' rules: [ { ruleId: '942130' // SQL Tautology (1=1) enabledState: 'Enabled' action: 'AnomalyScoring' exclusions: [ { matchVariable: 'RequestBodyJsonArgNames' selectorMatchOperator: 'StartsWith' selector: 'messages' // matches messages[0].content, [1], etc } ] } { ruleId: '942150' // SQL comment sequences enabledState: 'Enabled' action: 'AnomalyScoring' exclusions: [ { matchVariable: 'RequestBodyJsonArgNames' selectorMatchOperator: 'StartsWith' selector: 'messages' } ] } ] } ] } ] } } }
Azure CLI — the same exclusions, imperatively# Exclude the "messages" JSON arg family from rule 942130 (SQL Tautology) az network front-door waf-policy managed-rules exclusion add \ --policy-name PROD-WAF \ --resource-group rg-security-prod \ --type Microsoft_DefaultRuleSet \ --rule-group-id SQLI \ --rule-id 942130 \ --match-variable RequestBodyJsonArgNames \ --operator StartsWith \ --value messages # Repeat for the second contributing rule found in the logs az network front-door waf-policy managed-rules exclusion add \ --policy-name PROD-WAF \ --resource-group rg-security-prod \ --type Microsoft_DefaultRuleSet \ --rule-group-id SQLI \ --rule-id 942150 \ --match-variable RequestBodyJsonArgNames \ --operator StartsWith \ --value messages
Prefer values over names when the log gives you the choice

Where the log shows a value-level match (e.g. RequestBodyJsonArgValue), prefer excluding by value match variables over name match variables when both would technically work, and always prefer the most specific selector the operator set supports (Equals over StartsWith, StartsWith over Contains). Name-based exclusion by-name variables exist mainly for backward compatibility with older rule set versions; narrower value-based matching leaves less room for an unrelated field that happens to share a name prefix to slip through unexamined.

Multiple selectors in one exclusion are OR'd together

If your JSON structure has multiple fields that legitimately need the same treatment — messages[*].content and a separate system_prompt field, for example — you can list multiple exclusion selectors in a single exclusion block. They combine as an OR: a match on any one of them causes the WAF to skip evaluation for that specific rule on that specific field. This is more maintainable than five near-identical exclusion resources.

07Fix 3 — Custom Allow Rule Scoped by URI + HeaderThe Fix

This is the tool for the two cases field exclusions can't reach: an InitialBodyContents match (Section 3), or a payload so consistently triggering across so many rules that per-rule exclusions become an unmaintainable list. Custom rules are always evaluated before the DRS, and a request matching an Allow custom rule bypasses managed-rule inspection entirely for that request — nothing else in the DRS runs against it.

The precision that makes this safe is exactly what the brief asks for: scope the Allow to a specific request URI and a specific header that only your legitimate caller would send — never a bare Allow on a path alone, which any attacker could also reach.

Bicep — Allow rule scoped to URI + a service-specific header, evaluated before the DRSresource wafPolicy 'Microsoft.Network/frontDoorWebApplicationFirewallPolicies@2022-05-01' = { name: 'PROD-WAF' location: 'Global' properties: { customRules: { rules: [ { name: 'AllowAICodeReviewEndpoint' priority: 10 // LOW number = evaluated FIRST ruleType: 'MatchRule' action: 'Allow' matchConditions: [ { matchVariable: 'RequestUri' operator: 'BeginsWith' matchValue: [ '/v1/code-review' ] negateCondition: false } { matchVariable: 'RequestHeader' selector: 'X-Internal-Service' // header only your own operator: 'Equals' // backend service sets — matchValue: [ 'ai-code-review-svc' ] // never client-controlled negateCondition: false } ] } ] } managedRules: { /* ... DRS config unchanged ... */ } } }
Azure CLI — the same custom rule, imperativelyaz network front-door waf-policy rule create \ --policy-name PROD-WAF \ --resource-group rg-security-prod \ --name AllowAICodeReviewEndpoint \ --priority 10 \ --rule-type MatchRule \ --action Allow az network front-door waf-policy rule match-condition add \ --policy-name PROD-WAF \ --resource-group rg-security-prod \ --name AllowAICodeReviewEndpoint \ --match-variable RequestUri \ --operator BeginsWith \ --values "/v1/code-review" az network front-door waf-policy rule match-condition add \ --policy-name PROD-WAF \ --resource-group rg-security-prod \ --name AllowAICodeReviewEndpoint \ --match-variable RequestHeader \ --selector "X-Internal-Service" \ --operator Equals \ --values "ai-code-review-svc"
The header must be something a caller can't forge from outside

A custom rule that only checks the URI is a hole, not a fix — anyone on the internet can hit that path directly and skip WAF inspection entirely. The header condition is what makes this safe, but only if the header is genuinely unforgeable by an external caller: set it at your backend or via an internal gateway/APIM layer after Front Door, on a network path an external client cannot reach, or authenticate the header's value against a secret the client doesn't have. A header named X-Internal-Service that any client can set in their request is worse than no exclusion at all — it's a documented WAF bypass instruction.

Priority ordering: custom rules run low-to-high, before the DRS

Custom rule priority is evaluated in ascending order — lower numbers run first — and the DRS runs after all custom rules that would otherwise match. If a request matches your Allow rule, no other custom rule and no DRS rule evaluates it at all. This is powerful and therefore dangerous: keep custom Allow rules as narrow as the two-condition example above, and resist the temptation to widen the URI match "just in case" — every character you loosen in that BeginsWith value is attack surface with zero WAF coverage behind it.

Figure 2 — The tuning rollout: never tune directly against Prevention mode
FOUR STAGES — each one gated on evidence from the stage before itSTAGE 1 — Detection mode, log everything, block nothingRun for a minimum of 1-2 weeks to capture real traffic patterns, not a synthetic sample.STAGE 2 — Analyze logs; group false positives by rule + field + endpointDistinguish InitialBodyContents matches (needs custom rule) from field matches (needs exclusion).STAGE 3 — Deploy exclusions + custom rules, STILL in Detection modeConfirm the same payloads no longer generate log matches. Re-test attack payloads separately — they must STILL match.STAGE 4 — Switch to Prevention mode. Monitor closely for 48h post-cutover.
Tuning directly against a Prevention-mode policy means every mistake is a live outage. Running the full diagnose-and-tune cycle in Detection mode first — where blocks are logged but not enforced — turns each iteration into a safe rehearsal. Stage 3's second half matters as much as the first: a known-malicious test payload must still be blocked after your exclusions land, or you've tuned too broadly.
08Fix 4 — Detection Mode First, AlwaysDiscipline

Every fix in this article should be developed and validated in Detection mode before it ever touches a Prevention-mode policy in production. Detection mode logs every rule match exactly as Prevention mode would, but lets the request through — which means you can iterate on exclusions and custom rules against real traffic, see exactly what would have been blocked, and confirm your fix actually closes the gap, all without a single legitimate request failing in the meantime.

Bicep — a parameterized mode, safe for both stages of rolloutparam wafMode string = 'Detection' // flip to 'Prevention' only after Stage 3 clears resource wafPolicy 'Microsoft.Network/frontDoorWebApplicationFirewallPolicies@2022-05-01' = { name: 'PROD-WAF' location: 'Global' properties: { policySettings: { enabledState: 'Enabled' mode: wafMode } // ... managedRules, customRules as built in Sections 6-7 ... } }
Detection mode is not a substitute for testing attack payloads

The gap teams fall into: they build exclusions in Detection mode, confirm the legitimate payload no longer appears in the block log, and ship straight to Prevention. That confirms the false positive is gone — it says nothing about whether the exclusion accidentally also let a real attack payload through. Test both directions in Detection mode: the legitimate payload should stop generating a match; a genuine SQL injection payload aimed at the same field should still generate one. If your exclusion is too broad, the second test is where you'll see it — the log simply won't show the attack payload as matched, and that silence is the warning sign.

Rule set version upgrades reset your customizations

Worth flagging before it surprises anyone: when you change the ruleset version on a WAF policy (moving from DRS 2.0 to 2.1, for example), any rule overrides and exclusions you configured against the old version are reset to the defaults for the new version. Treat every ruleset version bump as a mini-migration — re-apply your exclusions against the new version's rule IDs (which can shift), and run the Detection-mode validation cycle again rather than assuming last quarter's tuning carried forward.

09Anti-Patterns: Tuning That Opens a HoleTraps

Because a WAF block is an urgent, visible incident — a customer-facing endpoint is down — the pressure to fix it fast pushes toward the broadest, quickest lever. These are the ones that trade security for speed without anyone deciding to make that trade explicitly.

Anti-patternWhy it feels rightWhy it isn't
Disable the entire SQLI rule group"It's blocking us, turn it off"Removes SQL injection protection for every endpoint behind the policy, not just the AI one — including endpoints that actually talk to a database
Set the whole policy to Detection mode permanently"Nothing gets blocked, problem solved"You've disabled Prevention entirely. Every real attack is now logged and let through, not just the false positives
Custom Allow rule on URI alone, no header check"One less condition, simpler rule"Anyone who knows or guesses the path bypasses WAF inspection completely — a documented, public bypass
Wildcard the exclusion selector broadly (e.g. bare *)"Covers whatever field triggers next time too"Excludes far more of the JSON body than the one field that actually needed it — new legitimate-looking attack vectors in adjacent fields go uninspected
Exclude at rule-set scope instead of single-rule scope"One exclusion instead of several"Removes inspection from that field for every rule in the entire DRS, not just the SQLI rules actually causing problems
Ship exclusions straight to Prevention, skip Detection validation"We're confident, let's just fix it"The first sign of an over-broad exclusion is a real attack getting through silently — you won't know until it's exploited, because nothing alerts on "successfully bypassed WAF"

Validation & Verification: Confirm the Fix

Two things must both be true for this fix to be real: the legitimate payload passes, and a genuine attack payload aimed at the same field still gets blocked. Verify both, in that order, before touching Prevention mode.

Step 1 — Replay the ORIGINAL blocked payload against the tuned policy# Use the exact payload from the log entry that started the incident. # Run this against a Detection-mode (or staging) instance of the policy. curl -s -o /dev/null -w "%{http_code}\n" \ -X POST "https://api.contoso.com/v1/chat/completions" \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "system", "content": "Given a users table with columns id, email, created_at -- write a query that returns all users created in the last 30 days" } ] }' # PASS: 200 OK (or your normal application response code). # FAIL: 403 - check the WAF log again; you may have hit the InitialBodyContents # gap (Section 3) and need the custom URI+header rule instead of an exclusion.
Step 2 — Confirm a REAL attack payload aimed at the same field is still blocked# Deliberately send a genuine SQL injection attempt at the identical field # you just excluded. This is the test that proves you didn't over-broaden it. curl -s -o /dev/null -w "%{http_code}\n" \ -X POST "https://api.contoso.com/v1/chat/completions" \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "1=1 UNION SELECT username, password FROM admin_users --" } ] }' # PASS: 403 Forbidden - the field is excluded from the TWO SPECIFIC RULES # that legitimate SQL-teaching payloads triggered, but other rules # (or the same rules with a different match pattern) still catch this. # FAIL: 200 OK - your exclusion is too broad. Narrow the selector or the # rule-ID list immediately; do not proceed to Prevention mode.
Step 3 — Confirm the custom Allow rule's header condition can't be forged externally# Attempt to set the "trusted" header directly from outside your network # boundary. This should FAIL to reach the backend if the header is truly # set only by an internal service. curl -s -o /dev/null -w "%{http_code}\n" \ -X POST "https://api.contoso.com/v1/code-review" \ -H "X-Internal-Service: ai-code-review-svc" \ -H "Content-Type: application/json" \ -d '{ "code_diff": "1=1 UNION SELECT * FROM secrets --" }' # If this externally-forged request is ALLOWED through and reaches the # backend, the header is not actually trustworthy - it's a public bypass # instruction. Move the header-setting logic to a layer the external # caller cannot reach, or authenticate its value against a secret.
Step 4 — KQL: confirm zero false positives for a full week post-cutoverAzureDiagnostics | where Category == "FrontdoorWebApplicationFirewallLog" | where TimeGenerated > ago(7d) | where requestUri_s has_any ("/v1/chat/completions", "/v1/code-review") | where action_s == "Block" | summarize count() by ruleName_s, matchVariableName_s | order by count_ desc // PASS: empty result, or only matches you have NOT yet reviewed/tuned. // FAIL: any row corresponding to a payload shape you believed was fixed - // means either a new legitimate pattern emerged, or the exclusion // didn't cover a variant of the field (e.g. messages[1] vs [0]).
What "fixed" actually means here

Three conditions must hold together. One: the exact payload that started the incident now passes, verified by replaying it, not by assumption. Two: a genuine attack payload aimed at the same field is still blocked — proof the exclusion is scoped to the false positive and nothing wider. Three: if a custom Allow rule is involved, its non-URI condition (the header, in this article's example) cannot be set or forged by an external caller — verified by attempting exactly that from outside your trust boundary. Miss any of the three and you either still have the outage, or you've quietly opened a hole that won't announce itself until it's exploited.

Key Takeaways

Anomaly scoring is additive across the whole request. Fixing the one rule visible in the log may not clear a block — a second, quieter match elsewhere in the same JSON body can still push the score past 5.
LLM payloads trigger SQLI rules because the tokens are identical, not because the intent is. A system prompt teaching SQL and a real injection attempt can share the exact same four characters.
InitialBodyContents matches cannot be excluded. If the log shows this matchVariableName, a field-level exclusion will not work — use a scoped custom Allow rule instead.
Scope exclusions to a single rule and a single field, never a rule set or rule group. Broader scope removes protection from every other endpoint and rule sharing that scope, not just the false positive.
Custom Allow rules need a header condition an external caller cannot forge. URI-only bypass rules are a documented, public hole — anyone who finds the path skips WAF inspection entirely.
Tune in Detection mode, always. Confirm the legitimate payload passes AND a real attack payload at the same field still blocks — before either fix ever reaches a Prevention-mode policy.
Ruleset version upgrades reset your customizations. Treat every DRS version bump as a mini-migration; re-apply exclusions and re-validate rather than assuming they carried forward.

Frequently Asked Questions

Why does my WAF block a system prompt that just teaches an LLM to write SQL?
Because the Default Rule Set's SQLI rules pattern-match on token shapes — SQL comment sequences, boolean tautologies like 1=1, keyword clusters — and those tokens look identical whether they appear in a real injection attempt or in a system prompt's instructional text. DRS 2.0 and later use anomaly scoring: each matching rule adds to a cumulative score, and any request reaching a score of 5 or more is blocked in Prevention mode. A single Critical-severity match reaches that threshold alone. The fix is not to disable SQL injection protection broadly — it's to exclude the specific field (for example, the JSON body's messages[0].content) from the specific rules it triggers, on the specific endpoint that legitimately needs to carry that content, while leaving every other field, rule, and endpoint fully protected.
I created an exclusion but the request is still being blocked. Why?
The most common reason is that the rule actually firing matched on InitialBodyContents or DecodedInitialBodyContents — the raw request body evaluated before it's parsed into named JSON or POST arguments. Azure's documentation is explicit that exclusions currently cannot be created for these two match variable types. Check the exact matchVariableName in your WAF logs; if it shows one of these values rather than a named field like RequestBodyJsonArgValue:fieldname, a field-level exclusion will never resolve the block, no matter how it's configured. In that situation, use a custom Allow rule scoped by request URI and an unforgeable header instead, which bypasses managed-rule inspection entirely for that specific, tightly-matched request rather than trying to exclude a field the WAF hasn't parsed out yet.
Is it safe to use a custom rule to bypass WAF inspection for an endpoint?
Only if the match conditions cannot be satisfied by an arbitrary external caller. A custom Allow rule matching on request URI alone is not safe — any client that knows or guesses the path receives the same bypass an authorized caller would, with zero WAF inspection behind it. The pattern that is safe combines the URI condition with a second condition — typically a specific request header — whose value is set only by your own backend or an internal gateway layer that an external client cannot reach or forge. Before relying on this pattern in production, deliberately attempt to set that header from outside your trust boundary and confirm the request is not treated as trusted. If it is, the header condition isn't providing the security property you're relying on.
Should I just disable the SQL injection rule group for my AI endpoints?
Not as a first move, and ideally not at all. Disabling a rule group removes SQL injection protection for every endpoint the WAF policy applies to, not just the AI-specific one — including any endpoint that genuinely talks to a database and is a real injection target. If your AI endpoint is on a dedicated WAF policy that applies to nothing else, disabling the group is a smaller blast radius than it would be on a shared policy, but a targeted field-level exclusion on the two or three specific rules actually causing false positives is almost always achievable and leaves far more real protection intact. Reserve full rule-group disablement for cases where you've confirmed, through Detection-mode testing, that the entire rule category is fundamentally incompatible with a specific endpoint's legitimate traffic shape — which is rare.

Popular posts from this blog

Learn how to use Azure Chaos Studio to simulate data center outages, test Azure OpenAI failover, and validate AI app resiliency using KQL and CLI workflows

Resiliency Testing Chaos Studio Zone Down Azure OpenAI Failover Testing AI Resiliency: Using Azure Chaos Studio to Simulate Data Center Outages on Your LLM Every multi-region Azure OpenAI architecture diagram has a failover arrow drawn on it. Almost none of them have ever actually been triggered. The arrow is a hypothesis, confirmed only by a real outage — unless you deliberately cause a controlled one first, on your own schedule, with a rollback plan, instead of finding out during an incident that the failover you designed never quite worked the way the diagram promised. The failure signature this guide resolves # The gap this article closes — a real architecture review finding: Design doc, page 4: "In the event of a regional outage, Azure Front Door automatically routes traffic to the secondary Azure OpenAI deployment in West Europe, with an expected failover time under 60 seconds." Verification performed to support this claim: NONE. Last time this path was ac...

Improve AI application performance by reducing latency, optimizing embeddings, and lowering cloud inference costs

Performance Fix Foundry Local 1.2 Linux ARM64 Embeddings Offline ASR The Edge Latency Drop: Fixing Latency Spikes by Offloading Embeddings to Foundry Local 1.2 You are paying a full cloud round trip — network, TLS, queue, throttle risk — to turn a twelve-word search query into a vector. That is the most expensive way possible to do one of the cheapest computations in your stack. Foundry Local 1.2 now runs on Linux ARM64, which means embeddings and speech recognition can happen on a Raspberry Pi, a Jetson, or a Graviton instance — offline, unmetered, and in single-digit milliseconds. The failure signature this guide resolves # Application Insights — the embedding call, not the LLM, is your tail latency: name p50 p95 p99 calls/day POST /embeddings (cloud) 89 ms 412 ms 3,847 ms 1,240,000 POST /chat/completions (cloud) 940 ms 1,720 ms 2,910 ms 38,000 ^^^^^^^^ ...

Learn how to select Azure Files and Blob storage tiers, avoid early deletion fees, model costs, and automate lifecycle management for large file migrations.

Choosing the Right Azure Storage Tier for Large File Migrations The complete decision framework for storage tier selection during large file migrations — Azure Files tiers, Blob tiers, cost modelling, early deletion traps, lifecycle automation, and the 2026 changes that affect every migration running today. By Francis Avorgbedor | Azure Engineer  ·  July 14, 2026  ·  18 min read  ·  Storage Tiers · Cost Optimisation · Migration FA Francis Avorgbedor Azure Engineer  ·  SEVENAI  ·  Azure Field Notes 9 Distinct Azure storage tiers across Files and Blob — most engineers know only 3 15hrs Archive tier rehydration time at standard priority — the delay teams forget to plan for 128KB Minimum billable object size for Cool/Cold/Archive from July 2026 — a 32× trap for small files 70% How much retrieval and transaction fees add to a theoretical Archive storage bill The most expensive mistake I see on large Azure file migrations is not choosing the w...

Find the hidden Windows 10 system files consuming up to 500GB, including hibernation, shadow copies, backups, and WinSxS, with safe cleanup steps.

  The 500GB System File That Eats Your Hard Drive Something on your Windows 10 drive is consuming hundreds of gigabytes and the normal tools cannot find it. This guide identifies every known culprit — from hibernation files and shadow copies to runaway backups and the Windows component store — and tells you exactly what is safe to delete, what to leave alone, and what the commands actually do.

Learn safe methods to reset Azure virtual machines using managed disks while preserving critical workloads

How to Reset an Azure Virtual Machine to Factory Settings Using a Managed Disk Azure does not have a single "factory reset" button. What it does have is something better: the OS Disk Swap — a method that swaps out the corrupted or misconfigured OS disk for a clean Windows Server managed disk without deleting the VM, its NICs, its IP addresses, or any attached data disks. Here is how it works, when to use it, and the exact steps to execute it safely. FA Francis Avorgbedor Azure Engineer July 16, 2026 15 min read Azure VMs · Windows Server · Real-World Fix 3 Methods to achieve a clean Windows Server installation on an existing Azure VM ~15min Typical OS Disk Swap duration — VM retains its NICs, IPs, and data disks throughout 0 Data disks affected by an OS Disk Swap — data disks remain attached and untouched 1 Snapshot of the original OS disk you must take before starting — no exceptions Introduction Why Azure Does Not Have a Simple Factory Reset — and What to Do Instead On a ph...

Determine Windows 11 compatibility, upgrade requirements, costs, and performance expectations on older hardware

Can I Update My Old Computer to Windows 11 — and How Much Will It Cost? Your i7, 16GB RAM, 512GB SSD machine is powerful enough to run Windows 11 comfortably. The TPM 2.0 and Secure Boot wall is a security checkbox, not a performance ceiling. Here are two proven ways to get past it, what each one costs, and what you are trading away by doing so. $0 Cost of the Windows 11 licence if your existing Windows 10 is genuine — the upgrade remains free in 2026 2 Proven methods to bypass TPM 2.0 and Secure Boot — Rufus (easy) and Registry edit (manual) 25H2 Current Windows 11 version — all known bypass methods tested and confirmed working as of July 2026 Oct 2025 Windows 10 end of life — no more security updates. Staying on Windows 10 now carries real risk. First — Check Your BIOS Before Anything Else You Might Not Actually Need a Bypass Before running any bypass, open your BIOS and look at two settings. Many computers that fail the Windows 11 compatibility check have TPM 2.0 present in the hard...

Solve common AKS issues with practical troubleshooting techniques for networking, scaling, upgrades, and workloads

Troubleshooting Guide AKS Kubernetes Real Solutions kubectl Azure Kubernetes Service (AKS) Troubleshooting Guide: Real Solutions to Common Problems CrashLoopBackOff at 2am. Pods stuck Pending with no obvious cause. Nodes going NotReady mid-deployment. DNS resolution silently failing in production. Every AKS engineer encounters these — the difference between engineers who panic and engineers who stay calm is knowing the exact sequence of diagnostic commands to run. This guide gives you that sequence, the root cause analysis for each failure mode, and the fix. 3 commands 90% of AKS problems are diagnosed with the same three kubectl commands: describe pod, logs --previous, and get events — in that order, every time Exit 137 The exit code that tells you everything: container killed by SIGKILL — either the Linux OOM killer (memory limit exceeded) or kubelet after grace period expired 5 min The CrashLoopBackOff ceiling: Kubernetes applies exponential backoff (10s → 20s → 40s → 80s → 160s → 3...

Step-by-step guide to deploying scalable AI chatbots on Azure with OpenAI and App Service

Step-by-Step Guide Azure OpenAI App Service Production Python How to Deploy an AI Chatbot on Azure Using Azure OpenAI and App Service From zero to a production-grade AI chatbot: provision Azure OpenAI, write a streaming Flask API backend, deploy it on Azure App Service with Managed Identity, wire in conversation history and content safety, and instrument it with Application Insights — all with complete code and Terraform IaC. No API keys in environment variables. No hardcoded secrets. No half-finished PoC patterns. 7 phases This guide covers the full deployment lifecycle: architecture design → resource provisioning → backend code → App Service deployment → streaming → security → monitoring Zero keys The chatbot authenticates to Azure OpenAI using Managed Identity and DefaultAzureCredential — no API keys stored in environment variables, Key Vault, or code SSE Server-Sent Events stream GPT tokens to the browser as they generate — the same token-by-token typing effect users expect from pr...