Build1 distinct publisher3 min readPublished
A gateway ACL and a backend router are separate parsers with no agreed decode order, which is why /%2Fadmin still lands. Normalizing earlier relocates the window; refusing ambiguous paths is what closes it.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The argument turns on %2F, and the spec cuts both ways there. RFC 3986 section 2.3 says normalizers SHOULD decode percent-encodings of unreserved characters [3]. Section 3.3 classifies %2F as reserved, a different thing from the / delimiter [4]. A normalizer that follows section 2.3 literally leaves /%2Fadmin encoded, and a literal ACL comparison against /admin then declines to match a string that is not /admin. What produces the bypass is the backend choosing to decode a reserved delimiter before it routes [1]. Section 6.2.2 is what makes both ends defensible: normalization is optional preprocessing with no required ordering [2]. The ordering silence is real, and the %2F class is additionally two parsers disagreeing about a character the spec put on the keep side of the line [18].
The whole disagreement fits inside one product. With `proxy_pass http://backend/`, nginx decodes and normalizes before forwarding; drop the path from the directive and it forwards the client string unchanged [5]. Same binary. The trailing slash is the security boundary. Gravitee ships RAW, REJECT and NORMALIZE as explicit modes, which is a vendor stating that there is no correct default to inherit [6].
The gateway-versus-backend framing is narrower than the evidence. Spring's CVE-2023-20860 is intra-framework: Spring Security's `mvcRequestMatcher` and the MVC dispatcher read the `**` wildcard differently inside one process [10]. ModSecurity's CVE-2024-1019 decodes %2F before it separates the path from the query string, so a WAF rule inspecting the path component never sees the payload [11]. APISIX's `uri-block` plugin compared `$request_uri` literally, so `//internal/` missed a `^/internal/` rule [12]. Six CVEs, five products, because Apache httpd appears twice [17].
/%252Fadmin is why decoding once is not the fix. The first decode turns %25 into %, producing %2Fadmin, which passes the ACL; the backend's decode produces /admin [14]. Apache showed what string-by-string patching costs: 2.4.49 decoded literal `..` but not %2e%2e [7], and the 2.4.50 fix normalized literal `..` while %2e%2e still bypassed the check, scoring 9.8 against the 7.5 it was meant to close [8][19]. Moving normalization to the edge relocates the mismatch rather than removing it, because the two parsers stay independent [15].
The strong version of the claim, that any HTTP stack with two or more parsers carries an inconsistency window sized by its pair of design decisions [16], is a design argument, not a measurement. The six CVEs are published bypasses in products with different parse pairs, not a census of exposure. The number transfers to a given stack only if the matcher and the router are separate implementations and the matcher evaluates the pre-decode string; where matching happens after decode in the same parser, the window for these four techniques closes [20]. The AWS HTTP API case, where a trailing slash matched the route but reached the Lambda authorizer with `userId` undefined and permitted unauthenticated wire transfers at a fintech, carries no CVE and rests on the dev.to account alone [13]. That leaves REJECT as the honest edge setting: a gateway cannot guarantee agreement with a parser it does not own, but it can refuse to forward a path two parsers would read differently [6][15].
Ranked by verification strength, evidence, and original report placement.
A request for /%2fadmin reaches a gateway whose ACL blocks /admin; the gateway sees an encoded string that does not match the literal /admin rule and passes it through, and the backend decodes first, routes second, and delivers the admin panel to the caller.
RFC 3986 section 6.2.2 describes normalization as optional preprocessing with no required ordering constraint.
RFC 3986 section 2.3 states that percent-encodings of unreserved characters SHOULD be decoded by normalizers, and the spec specifies no point in the middleware stack where this must happen, so each gateway decides independently.
RFC 3986 section 3.3 distinguishes %2F from / as reserved versus delimiter, and permits empty segments, making //admin valid syntax; backends with path cleaning collapse //admin to /admin.
With proxy_pass http://backend/, nginx decodes and normalizes before forwarding; without a path in the directive it forwards the raw client string unchanged. Same binary, two behaviours, one configuration difference.
Gravitee documents three explicit modes, RAW, REJECT and NORMALIZE, confirming there is no consensus on a correct default.
Distinct publishers with included, body-backed reporting in this cluster.
1 article · September 5, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
product
The renewal job goes green before the service ever reloads the certificate1 distinct publisher
build
One Self-Hosted Setup Cuts a $198 Platform Bill to a $24 Droplet1 distinct publisher
security
DPRK operators compiled their backdoor into the victim's own HAProxy build3 distinct publishers
build
Spring's milestone week carries more than 90 CVE fixes into your patch plan1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Identifiers check out, the anecdote doesn't
Everything here rests on one self-published dev.to post, but most of it is the kind of material that checks itself: named CVE identifiers with affected version ranges and CVSS scores, a GHSA reference for Quarkus, and RFC 3986 section numbers a reader can open in a second tab. The mechanism claims survive that scrutiny. The AWS story does not travel with the same paperwork, with no identifier, no advisory and no named fintech behind the unauthenticated wire transfers, and the piece's own arithmetic slips when it counts six CVEs across six tools while listing httpd twice.
Advisories across five products, no word on frequency
Recurrence is the strongest evidence in the story: five separate products have assigned identifiers for one parser disagreement, Gravitee sells the choice as three configurable modes, and AWS shipped a partial fix that left the authorizer half untouched. What nobody supplies is frequency. Beyond httpd's 2021 zero-day there is no exploitation telemetry, no count of exposed deployments, and no measure of how many stacks would fail the six-variant test if anyone ran it.
Core thesis stands despite some overselling
The structural argument earns its keep, and the corollary that gateway-side normalization relocates the window is the sharpest thing in the piece. The padding sits around it. Six CVEs become six tools when they are five products, the headline prints the gateway's view and the app's view as the same string, and the single case with a dollar consequence attached is also the only one with no advisory behind it. Trim those and the claim is smaller but intact.
A byline with nothing attached to sell
This is a practitioner post under a personal handle on dev.to, and it closes with a test procedure rather than a product. No vendor is being talked up, no tool is being sold, and the one commercial name in a favourable light, Gravitee, is cited for shipping three modes rather than for solving anything. The pressure acting on the text is reach and reputation, which explains the round-number framing more than it explains any of the technical content.
Sure of the mechanism, unsure how often it happens
We are comfortable with the parser-level account, because it is anchored in public advisories and spec text that hold up on their own. We are much less sure about scale and about the AWS narrative, and with dev.to alone on this there is no second account to catch either. Anyone acting on this should treat the four encodings and the log-comparison test as the durable part.