Close the Gaps in HTTP/2 Inspection: A Backend Playbook for ALB + AWS WAF and Origin-Layer Safety
HTTP/2 frame-splitting can blind WAFs. Configure ALB+WAF safely and add origin guards with code, checklists, and ops practices.
Sources
- https://aws.amazon.com/security/security-bulletins/2026-048-aws/
- https://docs.aws.amazon.com/elasticloadbalancing/latest/application/edit-target-group-attributes.html
- https://access.redhat.com/security/vulnerabilities/RHSB-2026-007
- https://nginx.org/en/docs/http/ngx_http_proxy_module.html
- https://go.dev/pkg/net/http/?m=old
Article Quality
Structured for practical reading: clear sections, useful depth, and enough context to revisit later.
Introduction
If your APIs sit behind a WAF, you probably assume every request the origin sees has already been fully inspected. That assumption becomes risky with HTTP/2. On June 29, 2026, AWS disclosed CVE-2026-13762 and CVE-2026-13763: under certain conditions, multi-frame HTTP/2 requests could be only partially inspected by AWS WAF on ALB, prompting a new configuration option to buffer frames before inspection. The CloudFront path was remediated server-side; ALB requires an explicit setting. This is a nudge to treat WAF as one layer, not the layer. (aws.amazon.com)
This guide turns that signal into an evergreen playbook. We’ll configure ALB + WAF safely, add origin-layer checks that don’t rely on any intermediary, and set up review and ops guardrails that survive future protocol quirks.
TL;DR
- Set ALB’s “WAF HTTP/2 traffic inspection behavior” to accumulate frames before inspection on request/response APIs; keep streaming endpoints on the default path. (docs.aws.amazon.com)
- Add origin-layer verification: content-type allowlists, max body size, decompression limits, schema validation, and signature checks before doing work.
- For self-managed proxies, prefer buffering request bodies for non-streaming routes (for example, NGINX proxy_request_buffering on) to avoid partial inspection gaps. (nginx.org)
- Build test cases for multi-frame payloads and smuggling-like edge cases; fail closed with 4xx when the body is incomplete, malformed, or oversized.
- Monitor with explicit SLOs: block ratio, 4xx/5xx by rule, and “inspection completeness” probes. Treat anomalies as production incidents, not scanner noise.
- Document a team workflow: who flips ALB attributes, how to test, how to roll back, and how to review proxy/origin diffs in code review.
Why It Matters
If any gateway, proxy, or CDN can forward requests the WAF didn’t fully inspect, your origin becomes the last line of defense. That’s not hypothetical—HTTP/2 has seen a run of parsing and compression pitfalls (for example, HPACK “HTTP/2 Bomb” DoS), and future protocol tweaks will show up again at the boundaries. Layered, explicit validation is the only stable defense. (access.redhat.com)
In practice: a well-configured ALB/WAF reduces attack surface, but origin-layer checks catch what escapes. You’ll prevent request smuggling variants, malformed framing, and payload tricks from reaching business logic.
The Practical Problem Engineers Need To Solve
You need to ensure that:
- WAF inspects the full HTTP/2 request body for standard APIs.
- Streaming endpoints (upload, long-lived bidirectional protocols) don’t regress latency or time out due to aggressive buffering.
- The origin enforces its own safety properties regardless of what any WAF, proxy, or library thought it saw.
Success means stable latency for legitimate clients, correct inspection for risky inputs, and clear rollback levers if something misbehaves.
Decision Framework: When To Buffer, When To Stream
Short answer: buffer and inspect for request/response APIs; stream where the client legitimately sends data over time.
-
Choose “inspect after sufficient data” at ALB when:
- The endpoint is a classic JSON/REST/GraphQL request/response, not a time-sensitive stream.
- You can tolerate a small buffering delay for full-body inspection. (docs.aws.amazon.com)
-
Keep default immediate inspection when:
- You operate true streaming or bidi workloads (live media, websockets-like semantics over HTTP/2, chunked uploads that require mid-stream responses).
- You have robust origin-layer validation that makes evasion via partial frames moot (see the implementation steps below).
Mistake to avoid: flipping everything to buffering. Make the choice per target group or per traffic class, not as a blanket rule.
Safe Workflow: From Inventory to Rollout
Brief answer: inventory endpoints, classify traffic, set ALB attributes for the right targets, harden the origin, and prove it with tests.
- Inventory and classify
- List endpoints and tag them as “request/response” or “streaming/bidi.”
- Note which target group each endpoint uses.
- Configure ALB + WAF for HTTP/2 inspection
- For request/response targets, set the WAF HTTP/2 inspection behavior to buffer frames before inspection. Keep streaming endpoints on the default setting. (docs.aws.amazon.com)
- Harden the origin
- Validate content-type, size limits, and schema before doing work.
- Require signatures or shared secrets for webhooks; compute HMACs on the raw body.
- Decompress safely; set conservative limits; reject unexpected encodings.
- Proxy baseline (self-managed)
- If you run NGINX/Haproxy/Caddy in front of origins, prefer buffering request bodies for non-streaming routes to avoid “partially seen” payloads at security filters. In NGINX, use proxy_request_buffering on per location for those routes. (nginx.org)
- Testing
- Build multi-frame and smuggling-inspired test cases. Verify WAF logs show complete inspection and the origin rejects malformed or oversized bodies.
- Release with a guardrail
- Launch per target group. Watch error budgets and 4xx/5xx by route. Keep a one-line rollback (CLI or IaC var) ready.
Concrete Implementation Steps (with commands)
Short answer: set the ALB target group attribute for inspection buffering where it fits, and add app-layer guardrails.
1) ALB target group attribute for HTTP/2 inspection
AWS exposes a target group attribute to decide how HTTP/2 request bodies are presented to WAF. For request/response endpoints, enable inspection after sufficient data has been accumulated. (docs.aws.amazon.com)
# Set ALB target group attribute for HTTP/2 WAF inspection
aws elbv2 modify-target-group-attributes \
--target-group-arn arn:aws:elasticloadbalancing:...:targetgroup/my-tg/1234567890abcdef \
--attributes "Key=waf.http2.traffic_inspection_behavior,Value=inspect_after_sufficient_data"- Confirm in the console: Target Groups → Attributes → “WAF HTTP/2 traffic inspection behavior.” (docs.aws.amazon.com)
Note: AWS’s June 29 bulletin clarifies CloudFront path was remediated server-side; ALB uses this attribute for safety. Apply it per target group that fronts request/response APIs. (aws.amazon.com)
2) NGINX front proxy for non-ALB setups
For non-streaming endpoints, ensure the proxy reads the full client body before forwarding to the upstream. This avoids partial inspection gaps caused by frame-splitting or tricky encodings. In NGINX:
# Example: Buffer request bodies for non-streaming API routes
server {
listen 443 ssl http2;
server_name api.example.com;
# For JSON APIs, buffer the request body before proxying
location /api/ {
proxy_request_buffering on; # ensure complete body seen at proxy
client_max_body_size 5m; # fail fast on oversized payloads
proxy_pass http://app_backend;
}
# For true streaming endpoints, keep defaults or explicitly allow streaming
location /upload/stream {
proxy_request_buffering off; # streaming: the app expects partial bodies mid-flight
proxy_pass http://uploader_backend;
}
}Reference: ngx_http_proxy_module directive “proxy_request_buffering.” (nginx.org)
3) Origin-layer safeguards in code
Enforce limits and validation even if upstream inspection failed, was bypassed, or behaved unexpectedly.
Go (net/http) example: limit size, set read deadlines, and fully read the body when appropriate.
// Safe JSON handler with tight limits and clear failure modes.
package main
import (
"encoding/json"
"errors"
"io"
"net/http"
"time"
)
const maxBody = 1 << 20 // 1 MiB
type Payload struct {
Action string `json:"action"`
Data any `json:"data"`
}
func safeJSON(w http.ResponseWriter, r *http.Request) {
// Defensive headers and timeouts
r.Context().Done()
rc := http.NewResponseController(w)
_ = rc // for Go 1.20+ features such as EnableFullDuplex if needed
// Enforce content-type
if r.Header.Get("Content-Type") != "application/json" {
http.Error(w, "unsupported media type", http.StatusUnsupportedMediaType)
return
}
// Bound the body and read it
r.Body = http.MaxBytesReader(w, r.Body, maxBody)
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
var p Payload
if err := dec.Decode(&p); err != nil {
var se *json.SyntaxError
if errors.As(err, &se) || errors.Is(err, io.EOF) {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
http.Error(w, "payload too large or invalid", http.StatusRequestEntityTooLarge)
return
}
// Reject trailing data
if dec.More() {
http.Error(w, "unexpected trailing data", http.StatusBadRequest)
return
}
// Normal processing...
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"ok":true}`))
}
func main() {
srv := &http.Server{
Addr: ":8080",
ReadTimeout: 5 * time.Second,
ReadHeaderTimeout: 2 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
http.HandleFunc("/api/do", safeJSON)
_ = srv.ListenAndServe()
}Note: In HTTP/2, the Go server can allow concurrent reads/writes in certain modes—don’t rely on that for correctness; validate first, respond second. (go.dev)
Express snippet: explicit size limit and rejection of unexpected content-types.
import express from "express";
import bodyParser from "body-parser";
const app = express();
// Limit is intentionally small; raise only where justified.
app.use(bodyParser.json({ limit: "1mb", type: "application/json" }));
// Strict content-type and schema checks before doing any work.
app.post("/api/do", (req, res) => {
if (!req.is("application/json")) {
return res.status(415).json({ error: "unsupported media type" });
}
const { action, data } = req.body || {};
if (typeof action !== "string") {
return res.status(400).json({ error: "invalid payload" });
}
return res.json({ ok: true });
});
app.listen(8080);Reference: Express/body-parser limit option. (expressjs.com)
Review Checklist (for every API behind a WAF)
Short answer: confirm inspection completeness, origin controls, and test coverage.
| Area | What to Check | Evidence of Done |
|---|---|---|
| ALB/WAF | For request/response TGs, attribute set to inspect-after-sufficient-data | CLI/console shows waf.http2.traffic_inspection_behavior configured; change is in IaC |
| Endpoint Class | Streaming vs request/response labeled per route | README or service catalog entry updated |
| Origin Limits | Content-type allowlist, body size, schema validation | Unit/integration tests show 4xx on bad input |
| Proxy Behavior | Buffering on non-streaming routes; off on streaming | NGINX/Haproxy config in repo; CI lints it |
| Logging | WAF and application logs correlated with a request ID | Log examples in PR description |
| Tests | Multi-frame and smuggling-like cases | Test files and CI artifacts attached |
Security Considerations
Short answer: never trust upstream interpretation. Validate at the origin and keep boundaries simple.
- Treat compression, framing, and transfer-encoding as untrusted. Fully reassemble and validate before using payload data.
- Keep decompression limits conservative to avoid zip/HPACK-like inflation.
- Don’t reflect raw inputs; template/escape everything.
- Use signatures for webhooks. If you need the raw body for HMAC, read it once and parse safely afterward.
- Monitor protocol error rates. A spike in 400/413/415 with high WAF activity might indicate evasions or test traffic. The “HTTP/2 Bomb” cycle showed how quickly new parsing/DoS variants propagate; assume iterations. (access.redhat.com)
For deeper egress hardening on Java backends, see the Spring InetAddressFilter playbook to limit where your code can call out—stopping SSRF at the source pairs well with strict ingress checks. /blog/java-ssrf-mitigation-spring-boot-inetaddressfilter-playbook
Performance and Operations Considerations
Short answer: buffer where safe, but watch tail latency and memory.
- Latency budgets: buffering adds a small delay; size it into SLOs. Track p90/p99 per route with histograms, not averages. /blog/java-production-metrics-micrometer-opentelemetry-histograms-slos
- Memory and temp files: proxies may spill to disk; ensure tmpfs or fast disk and quotas.
- Timeouts: set ReadHeaderTimeout and ReadTimeout on servers; tune client_body_buffer_size and upstream timeouts on proxies.
- Canary and rollback: change one target group at a time; roll back with a single CLI flag flip if error budgets burn.
- “Calm” operations: make the safe behavior the default in new services. Keep exceptions (true streaming) explicit and documented. /blog/building-calm-backend-systems
Common Mistakes (and better defaults)
Short answer: over-generalizing, hand-waving tests, and trusting middleware too much.
- One-size-fits-all buffering: streaming routes time out; classify endpoints first.
- Assuming WAF handled it: always verify at the origin; WAFs are rules + parsers, not omniscience.
- No decompression limits: attackers exploit huge ratio inputs. Set caps.
- Skipping multi-frame tests: if you never test framing tricks, you won’t detect them in prod.
- Confusing “works in staging” with “works at edge scale”: validate with real user-agents, CDNs, and ALB behaviors in the loop.
The Production Readiness Checklist
Short answer: flip features deliberately, prove safety, and watch the right dials.
- Inventory routes; tag streaming vs request/response.
- For request/response:
- ALB attribute waf.http2.traffic_inspection_behavior=inspect_after_sufficient_data set. (docs.aws.amazon.com)
- Origin checks: content-type allowlist, size limit, schema validation, signature/HMAC for webhooks.
- Proxy buffering on in non-streaming locations (if self-managed). (nginx.org)
- Tests:
- Multi-frame payload passes inspection and is accepted/rejected as expected.
- Oversized, malformed, and unexpected encodings fail with 4xx.
- Observability:
- SLOs and alerting for 4xx/5xx by route and WAF decision categories.
- Correlated request IDs between WAF and app logs.
- Rollback:
- One-liner to revert ALB attribute; IaC var toggle included.
- Ops runbook covers who/when/how.
Code Review Checklist
Short answer: review for correctness, then for performance.
- Does the endpoint’s classification (streaming vs request/response) match the ALB/NGINX config in the same PR?
- Are body size limits explicit and tested?
- Are content-types strictly validated?
- Are schemas versioned and validated before work begins?
- Are timeouts set at server and proxy?
- Are negative tests (multi-frame, malformed, oversized) included in CI?
How To Apply This In a Real Team
Short answer: give it owners, tests, and time on the calendar.
- Ownership: platform (ALB/WAF), networking (proxy), and service owners (origin) co-own this change. Assign named DRI per target group.
- Branch plan: one TG per PR; tests run with synthetic traffic generators that emulate multi-frame HTTP/2.
- Observability: add WAF-to-origin correlation in logs; run weekly health checks that confirm inspection completeness.
- Education: share a 30-minute internal talk with examples of payloads that looked fine to one parser and malicious to another.
- Documentation: service READMEs must state the endpoint class and safe defaults used.
- Related playbooks: when errors spike, follow the incident habits in my “calm systems” article. /blog/building-calm-backend-systems For error handling specifics on Java services, see the Spring Boot error handling guide. /blog/spring-boot-production-error-handling
A Realistic Failure Scenario
Short answer: partial inspection plus lenient origin equals data exfil or RCE.
- Setup: a JSON upload endpoint behind ALB+WAF; WAF looks at the first frames; origin accepts JSON and writes to storage.
- Attack: malicious content split across frames; WAF sees frame 1, origin gets the full payload.
- Impact: if the origin doesn’t enforce strict JSON schema, size, and encoding checks, deserialization bugs or template injection follow.
- Prevention: buffer-before-inspect at ALB for this route, strict origin validation, and CI tests that replay the multi-frame payload.
FAQs
Does setting “inspect after sufficient data” on ALB affect all traffic?
No. It applies per target group. Use it for request/response routes; keep default immediate inspection for streaming endpoints that can’t tolerate buffering. (docs.aws.amazon.com)
We’re on CloudFront + WAF. Do we still need to do anything?
AWS states the CloudFront path was remediated server-side for the bulletin. Still, origin-layer validation remains essential, and you should keep the same tests and observability because protocol edge cases recur. (aws.amazon.com)
How does this relate to recent HTTP/2 vulnerability waves?
They’re different issues but the lesson is similar: protocol complexity (HPACK, framing) creates inspection and resource-exhaustion pitfalls. Defense-in-depth plus explicit limits is the durable answer. (access.redhat.com)
Will buffering hurt upload latency?
A little for request/response routes; typically acceptable if you size timeouts and keep payload limits sane. For true streaming uploads, keep streaming on and rely more on origin validation.
Do I need to change NGINX everywhere?
Only where you manage your own front proxy and want to avoid partial inspection gaps. Use proxy_request_buffering on for non-streaming routes and clearly flag exceptions. (nginx.org)
How should we measure success?
Track SLOs: error budgets, WAF block ratios, 4xx/5xx by route, and synthetic “inspection completeness” probes. Use histograms and percentiles for latency and failure modes, not averages. /blog/java-production-metrics-micrometer-opentelemetry-histograms-slos
Conclusion
WAFs are excellent at buying you time and reducing noise, but they’re not a substitute for origin-layer safety. The June 29 ALB/WAF bulletin is a practical push to reset defaults: buffer and inspect for request/response APIs, stream only where you must, and validate everything at the origin. The rest is boring-but-reliable engineering—explicit limits, schema checks, negative tests, and SLOs—so your backend doesn’t hinge on how one parser decided to read a frame.
If you adopt nothing else today: flip the ALB attribute for your non-streaming targets, add one multi-frame test, and make origin validation a hard gate in code review. Your future self will thank you.
FAQ Schema
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Does setting “inspect after sufficient data” on ALB affect all traffic?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. It applies per target group. Use it for request/response routes and keep default immediate inspection for streaming endpoints that can’t tolerate buffering."
}
},
{
"@type": "Question",
"name": "We’re on CloudFront + WAF. Do we still need to do anything?",
"acceptedAnswer": {
"@type": "Answer",
"text": "AWS states the CloudFront path was remediated server-side for the bulletin. Still, origin-layer validation and the same tests and observability remain essential."
}
},
{
"@type": "Question",
"name": "How does this relate to recent HTTP/2 vulnerability waves?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Different issues, similar lesson: protocol complexity like HPACK and framing creates inspection pitfalls. Defense-in-depth plus explicit limits is the durable answer."
}
},
{
"@type": "Question",
"name": "Will buffering hurt upload latency?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Slightly for request/response routes and usually acceptable if you size timeouts and keep payload limits sane. For true streaming uploads, keep streaming and validate at origin."
}
},
{
"@type": "Question",
"name": "Do I need to change NGINX everywhere?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Only where you manage your own front proxy and want to avoid partial inspection gaps. Enable proxy_request_buffering for non-streaming routes and clearly flag exceptions."
}
},
{
"@type": "Question",
"name": "How should we measure success?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Track SLOs for WAF block ratios and 4xx/5xx by route, plus synthetic ‘inspection completeness’ probes. Use histograms and percentiles for latency and errors, not averages."
}
}
]
}