<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aniket Bhatia</title>
    <description>The latest articles on DEV Community by Aniket Bhatia (@aniket28dot).</description>
    <link>https://dev.to/aniket28dot</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4069757%2F2c72aa56-0ce1-441a-9965-ffed48d88bb6.png</url>
      <title>DEV Community: Aniket Bhatia</title>
      <link>https://dev.to/aniket28dot</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aniket28dot"/>
    <language>en</language>
    <item>
      <title>Rate Limiting: The Sliding Window Approach</title>
      <dc:creator>Aniket Bhatia</dc:creator>
      <pubDate>Sun, 09 Aug 2026 11:51:01 +0000</pubDate>
      <link>https://dev.to/aniket28dot/rate-limiting-the-sliding-window-approach-3fg2</link>
      <guid>https://dev.to/aniket28dot/rate-limiting-the-sliding-window-approach-3fg2</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkyb354mta79qbk7m2wb2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkyb354mta79qbk7m2wb2.png" alt="Rate Limiting desc" width="800" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every web application faces a fundamental challenge: how do you let legitimate users interact freely while preventing abuse? Without rate limiting, your endpoints are open to brute force attacks, denial-of-service flooding, scraping bots, and resource exhaustion that drives up infrastructure costs.&lt;/p&gt;

&lt;p&gt;Rate limiting is the gatekeeper. Here's how to build one that actually works.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Common approaches&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Fixed window counter&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The simplest approach. Divide time into fixed intervals (e.g., every calendar minute) and count requests in each. It's easy to implement, but it has a critical flaw: &lt;strong&gt;the boundary exploit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1y91o97he8q5eqymackg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1y91o97he8q5eqymackg.png" alt="FWC img" width="800" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Sliding log (precise but expensive)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Store the timestamp of every request. On each new request, count how many timestamps fall within the last N seconds. Perfectly accurate, but storing every timestamp is memory-intensive at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Sliding window (best of both worlds)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Uses a &lt;strong&gt;sorted set&lt;/strong&gt; to track request timestamps, continuously sliding the window forward. Old entries are pruned on every check. No boundary exploits, and memory stays bounded. This is the approach worth building.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;How the sliding window works&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Instead of resetting a counter at fixed intervals, maintain a sorted set where each member is a unique request ID and its score is the Unix timestamp. On every incoming request:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prune&lt;/strong&gt; — remove all entries older than &lt;code&gt;now - window_size&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count&lt;/strong&gt; — count remaining entries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide&lt;/strong&gt; — if count ≥ limit, deny; otherwise add the new request and allow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx05buxgjuybr3xfkidqe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx05buxgjuybr3xfkidqe.png" alt="How SW works?" width="799" height="223"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Implementation: Redis + Lua&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Redis sorted sets are ideal for this pattern. A Lua script ensures atomicity — no race conditions when multiple requests arrive simultaneously. Without atomicity, two concurrent requests could both see count=59 and both be allowed past a limit of 60.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- Step 1: Prune expired entries from the main window
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)

-- Step 2: Count remaining requests
local current = redis.call('ZCARD', key)
if current &amp;gt;= limit then
    return {0, current, limit}  -- DENIED
end

-- Step 3: Check burst window
redis.call('ZREMRANGEBYSCORE', burst_key, 0, now - burst_window)
local burst_current = redis.call('ZCARD', burst_key)
if burst_current &amp;gt;= burst_limit then
    return {-1, burst_current, burst_limit}  -- DENIED: burst
end

-- Step 4: Record the request in both windows
local member = tostring(now) .. ':' .. tostring(math.random(1000000))
redis.call('ZADD', key, now, member)
redis.call('EXPIRE', key, window + 1)
redis.call('ZADD', burst_key, now, member)
redis.call('EXPIRE', burst_key, burst_window + 1)

return {1, current + 1, limit}  -- ALLOWED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why a random suffix on members?&lt;/strong&gt; Sorted sets require unique members. Two requests arriving at the exact same millisecond would overwrite each other without it, causing undercounting.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Dual-layer protection&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A single window limit has a blind spot: a client could exhaust all 60 requests in the first 2 seconds and go silent for 58. The average rate looks fine, but the spike hammers your server. The solution is two checks that must both pass.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Layer&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Window&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Limit&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Prevents&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Window&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;60 seconds&lt;/td&gt;
&lt;td&gt;60 requests&lt;/td&gt;
&lt;td&gt;Sustained abuse across time boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Burst&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5 seconds&lt;/td&gt;
&lt;td&gt;10 requests&lt;/td&gt;
&lt;td&gt;Rapid-fire spikes (bots hammering in 1s)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;FastAPI middleware integration&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The rate limiter works best as middleware — intercepting every request before it reaches application logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@app.middleware('http')
async def rateLimitMiddleware(request, call_next):
    # Skip bypass paths (health checks, login)
    if request.url.path in bypass_paths:
        return await call_next(request)

    # Key by user (authenticated) or IP (anonymous)
    user = get_authenticated_user(request)
    rate_key = f"ratelimit:user:{user}" if user else f"ratelimit:ip:{ip}"

    result = await redis.eval(LUA_SCRIPT, keys=[rate_key, burst_key], args=[...])

    if result[0] == 0:
        return JSONResponse({"error": "Rate limit exceeded."},
            status_code=429, headers={"Retry-After": "60"})
    if result[0] == -1:
        return JSONResponse({"error": "Too many requests. Slow down."},
            status_code=429, headers={"Retry-After": "5"})

    response = await call_next(request)
    response.headers["X-RateLimit-Remaining"] = str(limit - result[1])
    return response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Decision&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Rationale&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Per-user key (authenticated)&lt;/td&gt;
&lt;td&gt;Prevents one user from affecting others&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-IP fallback (unauthenticated)&lt;/td&gt;
&lt;td&gt;Protects public endpoints from anonymous abuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fail-open on Redis errors&lt;/td&gt;
&lt;td&gt;Avoids blocking legitimate traffic if Redis goes down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP 429 + &lt;code&gt;Retry-After&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Standard; well-handled by clients and load balancers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;X-RateLimit-*&lt;/code&gt; headers&lt;/td&gt;
&lt;td&gt;Gives clients visibility into their remaining quota&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Testing&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A quick smoke test to verify your burst limit fires correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Send 15 rapid requests — expect 429 after the 10th
for i in $(seq 1 15); do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
    -H "Cookie: session=YOUR_COOKIE" https://your-app/api/me)
  echo "Request $i: HTTP $STATUS"
done

# Inspect the sorted set directly
redis-cli ZCARD ratelimit:user:alice@example.com
redis-cli ZRANGE ratelimit:user:alice@example.com 0 -1 WITHSCORES
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;Summary&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fixed window&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Simple but vulnerable to boundary exploits. Low memory, approximate accuracy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sliding log&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Exact accuracy, no boundary issues. High memory cost at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sliding window ✓&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Exact accuracy, no boundary exploits, moderate memory. The right choice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Rate limiting isn't just about counting requests — it's about counting them in the right window.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>backend</category>
      <category>webdev</category>
      <category>vulnerabilities</category>
      <category>fastapi</category>
    </item>
    <item>
      <title>Mitigating HTTP Request Smuggling</title>
      <dc:creator>Aniket Bhatia</dc:creator>
      <pubDate>Sun, 09 Aug 2026 11:17:04 +0000</pubDate>
      <link>https://dev.to/aniket28dot/mitigating-http-request-smuggling-be6</link>
      <guid>https://dev.to/aniket28dot/mitigating-http-request-smuggling-be6</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;When malformed or abnormal HTTP requests are interpreted by one or more entities in the data flow between the user and the web server, such as a proxy or firewall, they can be interpreted inconsistently, allowing the attacker to "smuggle" a request to one device without the other device being aware of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;HTTP Request Smuggling&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;HTTP Request Smuggling exploits discrepancies in how front-end (proxy/load balancer/WAF) and back-end servers parse HTTP requests, specifically around&amp;nbsp;&lt;strong&gt;where one request ends and the next begins&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How It Works&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;HTTP/1.1 uses two headers to indicate body length:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Content-Length&lt;/code&gt;&amp;nbsp;(CL):&lt;/strong&gt;&amp;nbsp;specifies body size in bytes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Transfer-Encoding&lt;/code&gt;&amp;nbsp;(TE):&lt;/strong&gt;&amp;nbsp;uses chunked encoding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a request contains&amp;nbsp;&lt;strong&gt;both&lt;/strong&gt;&amp;nbsp;headers (or malformed variants), different servers may prioritize differently:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variant&lt;/th&gt;
&lt;th&gt;Front-end uses&lt;/th&gt;
&lt;th&gt;Back-end uses&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CL.TE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Content-Length&lt;/td&gt;
&lt;td&gt;Transfer-Encoding&lt;/td&gt;
&lt;td&gt;Front-end forwards extra data that back-end treats as a new request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TE.CL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transfer-Encoding&lt;/td&gt;
&lt;td&gt;Content-Length&lt;/td&gt;
&lt;td&gt;Back-end stops reading early; leftover bytes become the next request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TE.TE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transfer-Encoding&lt;/td&gt;
&lt;td&gt;Transfer-Encoding&lt;/td&gt;
&lt;td&gt;One server is tricked by an obfuscated TE header (e.g.,&amp;nbsp;&lt;code&gt;Transfer-Encoding: chunked\r\n Transfer-Encoding: x&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Example (CL.TE)&lt;/strong&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variant&lt;/th&gt;
&lt;th&gt;Front-end uses&lt;/th&gt;
&lt;th&gt;Back-end uses&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CL.TE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Content-Length&lt;/td&gt;
&lt;td&gt;Transfer-Encoding&lt;/td&gt;
&lt;td&gt;Front-end forwards extra data that back-end treats as a new request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TE.CL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transfer-Encoding&lt;/td&gt;
&lt;td&gt;Content-Length&lt;/td&gt;
&lt;td&gt;Back-end stops reading early; leftover bytes become the next request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TE.TE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Transfer-Encoding&lt;/td&gt;
&lt;td&gt;Transfer-Encoding&lt;/td&gt;
&lt;td&gt;One server is tricked by an obfuscated TE header (e.g.,&amp;nbsp;&lt;code&gt;Transfer-Encoding: chunked\r\n Transfer-Encoding: x&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Example (CL.TE)&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt;
&lt;span class="na"&gt;Content-Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;13&lt;/span&gt;
&lt;span class="na"&gt;Transfer-Encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;chunked&lt;/span&gt;

0\r\n
\r\n
SMUGGLED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The front-end reads 13 bytes (the full body). The back-end processes chunked encoding, sees&amp;nbsp;&lt;code&gt;0&lt;/code&gt;&amp;nbsp;(end of chunks), and treats&amp;nbsp;&lt;code&gt;SMUGGLED&lt;/code&gt;&amp;nbsp;as the&amp;nbsp;&lt;strong&gt;start of the next request&lt;/strong&gt;&amp;nbsp;— which could be crafted to bypass access controls, poison caches, or hijack other users' requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Impact&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bypass security controls&lt;/strong&gt;&amp;nbsp;(WAF, authentication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache poisoning&lt;/strong&gt;&amp;nbsp;— serve malicious content to other users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session hijacking&lt;/strong&gt;&amp;nbsp;— prepend attacker-controlled headers to another user's request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credential theft&lt;/strong&gt;&amp;nbsp;via request redirection&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Mitigations &amp;amp; Solutions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure level:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use HTTP/2 end-to-end&lt;/strong&gt;&amp;nbsp;— HTTP/2 has a binary framing layer that eliminates ambiguity in request boundaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalize requests at the front-end&lt;/strong&gt;&amp;nbsp;— ensure the proxy resolves ambiguous requests before forwarding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disable connection reuse&lt;/strong&gt;&amp;nbsp;between front-end and back-end (performance trade-off but eliminates the attack surface)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the same web server software&lt;/strong&gt;&amp;nbsp;on all layers to ensure consistent parsing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Configuration level:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;5.&amp;nbsp;&lt;strong&gt;Reject ambiguous requests&lt;/strong&gt;&amp;nbsp;— configure proxies/servers to return&amp;nbsp;&lt;code&gt;400 Bad Request&lt;/code&gt;&amp;nbsp;when both&amp;nbsp;&lt;code&gt;Content-Length&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;Transfer-Encoding&lt;/code&gt;&amp;nbsp;are present&lt;/p&gt;

&lt;p&gt;6.&amp;nbsp;&lt;strong&gt;Disable&amp;nbsp;&lt;code&gt;Transfer-Encoding: chunked&lt;/code&gt;&lt;/strong&gt;&amp;nbsp;if not needed&lt;/p&gt;

&lt;p&gt;7.&amp;nbsp;&lt;strong&gt;Configure WAF rules&lt;/strong&gt;&amp;nbsp;to detect and block requests with conflicting length indicators&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application level:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;8.&amp;nbsp;&lt;strong&gt;Validate incoming requests&lt;/strong&gt;&amp;nbsp;— reject requests with duplicate or conflicting&amp;nbsp;&lt;code&gt;Content-Length&lt;/code&gt;/&lt;code&gt;Transfer-Encoding&lt;/code&gt;&amp;nbsp;headers&lt;/p&gt;

&lt;p&gt;9.&amp;nbsp;&lt;strong&gt;Use strict HTTP parsing&lt;/strong&gt;&amp;nbsp;in your web framework (e.g., Gunicorn's&amp;nbsp;&lt;code&gt;--strip-header-spaces&lt;/code&gt;, Nginx's&amp;nbsp;&lt;code&gt;ignore_invalid_headers off&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;10.&amp;nbsp;&lt;strong&gt;Set timeouts on back-end connections&lt;/strong&gt;&amp;nbsp;to limit the window for smuggled requests to persist&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Detection:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;11.&amp;nbsp;&lt;strong&gt;Monitor for anomalies&lt;/strong&gt;&amp;nbsp;— unusual&amp;nbsp;&lt;code&gt;400&lt;/code&gt;/&lt;code&gt;405&lt;/code&gt;&amp;nbsp;errors, mismatched access logs between front-end and back-end&lt;/p&gt;

&lt;p&gt;12.&amp;nbsp;&lt;strong&gt;Use tools like Burp Suite's HTTP Request Smuggler&lt;/strong&gt;&amp;nbsp;extension for testing&lt;/p&gt;

&lt;p&gt;The most effective fix is&amp;nbsp;&lt;strong&gt;upgrading to HTTP/2 between all hops&lt;/strong&gt;&amp;nbsp;and&amp;nbsp;&lt;strong&gt;rejecting ambiguous requests at the edge&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Why Both Headers = Smuggling Risk&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The HTTP/1.1 spec (RFC 7230) says: if both&amp;nbsp;&lt;code&gt;Transfer-Encoding&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;Content-Length&lt;/code&gt;&amp;nbsp;are present,&amp;nbsp;&lt;code&gt;Transfer-Encoding&lt;/code&gt;&amp;nbsp;&lt;strong&gt;must&lt;/strong&gt;&amp;nbsp;take priority and&amp;nbsp;&lt;code&gt;Content-Length&lt;/code&gt;&amp;nbsp;&lt;strong&gt;must&lt;/strong&gt;&amp;nbsp;be ignored. The problem is&amp;nbsp;&lt;strong&gt;not all servers follow this rule the same way&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Core Issue: Disagreement on Request Boundaries&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When a proxy sits in front of a backend server,&amp;nbsp;&lt;strong&gt;both need to agree on where each request starts and ends&lt;/strong&gt;. These two headers are the only way to determine that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Content-Length: 50&lt;/code&gt;&lt;/strong&gt;&amp;nbsp;→ "the body is exactly 50 bytes"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Transfer-Encoding: chunked&lt;/code&gt;&lt;/strong&gt;&amp;nbsp;→ "the body is split into chunks, ending with a&amp;nbsp;&lt;code&gt;0\r\n&lt;/code&gt;&amp;nbsp;chunk"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When&amp;nbsp;&lt;strong&gt;both&lt;/strong&gt;&amp;nbsp;are present, one server might use&amp;nbsp;&lt;code&gt;Content-Length&lt;/code&gt;&amp;nbsp;and the other might use&amp;nbsp;&lt;code&gt;Transfer-Encoding&lt;/code&gt;&amp;nbsp;— so they&amp;nbsp;&lt;strong&gt;see different request boundaries&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Concrete Example&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt;
&lt;span class="na"&gt;Content-Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;6&lt;/span&gt;
&lt;span class="na"&gt;Transfer-Encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;chunked&lt;/span&gt;

0\r\n
\r\n
X
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What the proxy sees&lt;/strong&gt;&amp;nbsp;(if it trusts&amp;nbsp;&lt;code&gt;Content-Length: 6&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Body = "0\r\n\r\nX"   (6 bytes — one complete request, forwards everything)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the backend sees&lt;/strong&gt;&amp;nbsp;(if it trusts&amp;nbsp;&lt;code&gt;Transfer-Encoding: chunked&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Chunk: "0" → end of chunks → request is done&lt;br&gt;
Leftover: "X" → this must be the START of a NEW request!&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That&amp;nbsp;&lt;code&gt;X&lt;/code&gt;&amp;nbsp;is now&amp;nbsp;&lt;strong&gt;smuggled&lt;/strong&gt;. The attacker controls it, and it gets prepended to the&amp;nbsp;&lt;strong&gt;next legitimate user's request&lt;/strong&gt;. In practice,&amp;nbsp;&lt;code&gt;X&lt;/code&gt;&amp;nbsp;would be a full malicious request like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GET /admin HTTP/1.1&lt;br&gt;
Host: example.com&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Why This is Dangerous&lt;/strong&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attack&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bypass WAF/auth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The proxy checks the outer request (looks safe), but the backend executes the hidden inner request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Session hijacking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The smuggled fragment merges with the next user's request, so the attacker's headers (e.g., a redirect) get applied to that user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache poisoning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The smuggled request causes the cache to store attacker-controlled content under a legitimate URL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Why Rejecting Both Headers Fixes It&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If&amp;nbsp;&lt;strong&gt;neither&lt;/strong&gt;&amp;nbsp;server ever processes a request containing both headers (returns&amp;nbsp;&lt;code&gt;400&lt;/code&gt;&amp;nbsp;immediately), there's no ambiguity to exploit — both sides always agree on how to parse the body length. That's exactly what the Nginx rule we added does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# If Transfer-Encoding AND Content-Length are both present → reject&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$smuggle_check&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"TECL")&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No ambiguity = no smuggling.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Testing HTTP Request Smuggling Protection&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Validate Nginx Config Syntax&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Should output:&amp;nbsp;&lt;code&gt;syntax is ok&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;test is successful&lt;/code&gt;. Then reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;strong&gt;Test That Ambiguous Requests Are Rejected (400)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Send a request with both headers using&amp;nbsp;&lt;code&gt;curl&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# This should return HTTP 400&lt;/span&gt;
curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://your-domain.com/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Transfer-Encoding: chunked"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Length: 6"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"0&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="s2"&gt;X"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected:&amp;nbsp;&lt;strong&gt;HTTP/1.1 400 Bad Request&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test with a normal request (should still work):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Content-Length only — should return 200/normal response&lt;/span&gt;
curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://your-domain.com/api/some-endpoint &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"key": "value"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;## Transfer-Encoding only — should also work&lt;/span&gt;
curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://your-domain.com/api/some-endpoint &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Transfer-Encoding: chunked"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"key": "value"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: Normal responses (not 400).&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Test with Python Script&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;

&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-domain.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;

&lt;span class="c1"&gt;## Craft an ambiguous request with both headers
&lt;/span&gt;&lt;span class="n"&gt;smuggle_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST / HTTP/1.1&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Host: {}&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Length: 6&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Transfer-Encoding: chunked&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_default_context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_connection&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap_socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server_hostname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;smuggle_request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;## Should print "400 Bad Request" in the response
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;strong&gt;Check Nginx Logs for Blocked Attempts&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Watch for 400 errors from smuggling attempts&lt;/span&gt;
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/access.log | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;" 400 "&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;strong&gt;Summary Checklist&lt;/strong&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Expected Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nginx -t&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;syntax ok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request with both&amp;nbsp;&lt;code&gt;CL&lt;/code&gt;&amp;nbsp;+&amp;nbsp;&lt;code&gt;TE&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;400 Bad Request&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request with only&amp;nbsp;&lt;code&gt;Content-Length&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Normal response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request with only&amp;nbsp;&lt;code&gt;Transfer-Encoding&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Normal response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Burp Suite smuggling scan&lt;/td&gt;
&lt;td&gt;No vulnerabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>api</category>
      <category>cybersecurity</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
