Repository navigation
1.0.4 opened TLS to post-quantum key exchange and made refused handshakes visible. 1.0.5 is one
forwarding fix, found in real traffic the day after that release: every /@username/ page 404'd
through the gateway while the same request direct to the origin returned 200.
What was happening
The gateway matches location on the decoded path (so /a%2fb cannot smuggle a segment, so
dot-segments collapse before they hit the filesystem), and it re-encodes that decoded path before
forwarding it. The re-encoder was ngx.escape_uri's set — unreserved characters only. Applied to a
path segment it escapes far more than the syntax requires:
client -> gateway -> origin
/@yinglink/ /%40yinglink/ 404
/a+b /a%2Bb wrong resource
/a:b /a%3Ab wrong resource
@ is a legal pchar in RFC 3986. nginx forwards what the client sent; we were rewriting it. The
origin (and the WAF in front of it) keys user pages on the literal @, so homepages, avatars and
banner images all 404'd — and no amount of cache clearing or config juggling could show it, because
proxy_pass ...$request_uri goes through the same encoder.
The fix
Forwarding now encodes over pchar: unreserved (A-Za-z0-9 - . _ ~), sub-delims
(! $ & ' ( ) * + , ; =) and : @ pass through untouched, and exactly the bytes that must not
go raw are escaped — %, #, ?, space, control bytes and non-ASCII. % staying in the escaped
set is deliberate: the input is already decoded, so a literal % the client sent has to reach the
origin as %25, otherwise the origin decodes a second, different set of escapes (%2F into /,
%23 into #) and the two sides end up serving different resources — the same argument that makes
the upgrade path forward the raw target.
Both forward seams share the one helper now (the proxied request URI for h1/h2/h3 and the WebSocket
upgrade fallback request line), and so do the directory-listing hrefs, which were producing
/%40name/ links for files whose names start with @.
ngx.escape_uri / ngx.unescape_uri for Lua scripts keep their strict OpenResty semantics — that
set is for serializing query arguments, not for path segments.
Evidence
tools/uri_encode_e2e.py(new, and now in CI's functional-contract step) starts an origin that
reports the raw request line it received and asserts on 14 shapes: 7/14 before the change,
14/14 after. The seven that were red are exactly the@ : + , ; = & $family.proxy.rsunit test extended to the pchar round-trip; the%25and#/?guards are asserted
in both directions so this cannot silently regress.- Unit tests 504/504; full local regression TOTAL 97 scripts, nonzero=0 with
SKIPempty
(the roll went from 96 to 97 becauseuri_encodeis now part of it); the packaged artefacts are
checked bytools/release_verify.pydriving the unpacked archive, not the build tree's binary.
Upgrading
Drop-in: replace the binary, nothing in the configuration changes. There is no new directive, no
new metric, and no behaviour change for any path that does not contain pchar characters the client
sent literally — paths with spaces, non-ASCII names, %-escapes and #/? go out exactly as they
did in 1.0.4.
onrouter -V
# ONRouter/1.0.5 (Rust, OpenResty-compatible)
# crypto: rustls/ring [pq=off]The eight platform archives below contain only onrouter and orr-gencert; hashes are in
SHA256SUMS.txt (sha256sum -c SHA256SUMS.txt). The "run it out of the box" bundle (conf, examples,
docs, generated demo certificate) is built on the release machine — see docs/OPERATIONS.md §2.1.