Skip to content

Tell the runtime whether routers deliver escaped path parameter values#2457

Merged
mromaszewicz merged 1 commit into
mainfrom
fix/issue-2455
Jul 12, 2026
Merged

Tell the runtime whether routers deliver escaped path parameter values#2457
mromaszewicz merged 1 commit into
mainfrom
fix/issue-2455

Conversation

@mromaszewicz

Copy link
Copy Markdown
Member

Closes: #2455

Generated server wrappers bound path parameters without knowing whether the router had already percent-decoded them, so the runtime's unconditional unescape double-decoded values containing literal percent signs ("discount%20" arrived as "discount ", "15%off" failed binding). The wrappers now set the ValueIsUnescaped option added in runtime v1.5.0, which is the new minimum runtime version for generated code.

The per-framework behavior was determined empirically through the parameter round-trip harness, and falls into three classes:

  • echo, echo v5 and chi route on URL.RawPath when it is non-empty, so they deliver decoded values normally but raw values exactly when the parameter contains an encoded slash (net/url only preserves RawPath for %2F-class escapes). Their wrappers pass the flag dynamically: ValueIsUnescaped: .URL.RawPath == "".
  • gin, gorilla, iris and stdhttp always deliver decoded values (the first three route on the decoded path and reject encoded slashes with a 404); their wrappers pass ValueIsUnescaped: true.
  • fiber v2 and v3 deliver raw values; their wrappers leave the flag unset so the runtime keeps unescaping.

Test enhancements for the above:

  • The round-trip harness gains a styled string path parameter (/simpleString/{param}) exercised with escape-hostile values — literal percents, spaces, reserved characters, unicode, an encoded slash, and a combined percent-plus-encoded-slash value — covering both branches of the dynamic flag. Frameworks that structurally cannot route encoded slashes assert the 404 so semantic changes in a framework surface here.
  • Fiber v3 gets a full round-trip harness leaf (generated server, implementation, and test), bringing it to parity with the other frameworks rather than being covered only by webhook tests.
  • stdhttp gets a single-route escaping probe, since its full round-trip run is still skipped on the digit-leading parameter panic (stdhttp: path parameter names starting with digits panic stdlib ServeMux #2306).

@mromaszewicz
mromaszewicz requested a review from a team as a code owner July 12, 2026 23:43
@mromaszewicz mromaszewicz added bug Something isn't working notable changes Used for release notes to highlight these more highly labels Jul 12, 2026
@socket-security

socket-security Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedgithub.com/​oapi-codegen/​runtime@​v1.4.2 ⏵ v1.5.098 +1100100100100

View full report

Closes: #2455

Generated server wrappers bound path parameters without knowing whether
the router had already percent-decoded them, so the runtime's
unconditional unescape double-decoded values containing literal percent
signs ("discount%20" arrived as "discount ", "15%off" failed binding).
The wrappers now set the ValueIsUnescaped option added in runtime
v1.5.0, which is the new minimum runtime version for generated code.

The per-framework behavior was determined empirically through the
parameter round-trip harness, and falls into three classes:

- echo, echo v5 and chi route on URL.RawPath when it is non-empty, so
  they deliver decoded values normally but raw values exactly when the
  parameter contains an encoded slash (net/url only preserves RawPath
  for %2F-class escapes). Their wrappers pass the flag dynamically:
  ValueIsUnescaped: <request>.URL.RawPath == "".
- gin, gorilla, iris and stdhttp always deliver decoded values (the
  first three route on the decoded path and reject encoded slashes with
  a 404); their wrappers pass ValueIsUnescaped: true.
- fiber v2 and v3 deliver raw values; their wrappers leave the flag
  unset so the runtime keeps unescaping.

Test enhancements for the above:

- The round-trip harness gains a styled string path parameter
  (/simpleString/{param}) exercised with escape-hostile values —
  literal percents, spaces, reserved characters, unicode, an encoded
  slash, and a combined percent-plus-encoded-slash value — covering
  both branches of the dynamic flag. Frameworks that structurally
  cannot route encoded slashes assert the 404 so semantic changes in a
  framework surface here.
- Fiber v3 gets a full round-trip harness leaf (generated server,
  implementation, and test), bringing it to parity with the other
  frameworks rather than being covered only by webhook tests.
- stdhttp gets a single-route escaping probe, since its full round-trip
  run is still skipped on the digit-leading parameter panic (#2306).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a double-decoding bug (issue #2455) where path parameters containing literal percent signs were silently corrupted — e.g. "discount%20" arrived at handlers as "discount ". The fix passes the new ValueIsUnescaped flag from runtime v1.5.0 to BindStyledParameterWithOptions, telling the runtime whether the router has already decoded the path parameter value before the wrapper sees it.

  • Per-framework flag strategy: chi, echo, and echo v5 use a dynamic check (r.URL.RawPath == ""), because Go's net/url only preserves RawPath for %2F-class escapes, which is exactly when these routers deliver raw values; gin, gorilla, iris, and stdhttp always deliver decoded values and receive ValueIsUnescaped: true; fiber v2/v3 deliver raw values and intentionally omit the flag.
  • Generated fixture regeneration: all strict-server and roundtrip harness *.gen.go files are correctly regenerated; the previous-thread concern about chi/echo strict servers hardcoding true is now resolved.
  • Test coverage: a testStringEscaping sub-suite is integrated into every framework's round-trip test, exercising literal percents, spaces, unicode, encoded slashes, and the cross-term value 50% off/sale; a dedicated TestStdHttpStringEscaping test sidesteps the digit-leading-param panic; fiber v3 gains a complete round-trip harness leaf.

Confidence Score: 5/5

Safe to merge — the change is a targeted bug fix with correct per-framework handling, regenerated fixtures that match the new templates, and comprehensive round-trip test coverage for every backend.

All seven backend templates are updated with the correct ValueIsUnescaped value (three with a dynamic RawPath check, three with true, two intentionally left unset). The strict-server fixtures previously flagged in review with hardcoded true for chi/echo/echo5 are now correctly regenerated. The runtime version bump is justified and explicitly documented. The new testStringEscaping harness covers the full space of tricky values empirically, and fiber v3 now has a complete round-trip leaf on par with other frameworks.

No files require special attention — all templates, their generated counterparts, and the test harness are consistent.

Important Files Changed

Filename Overview
pkg/codegen/templates/chi/chi-middleware.tmpl Adds ValueIsUnescaped: r.URL.RawPath == "" for styled path params — correct dynamic flag, since chi routes on RawPath when non-empty (only for %2F-class escapes) and on Path otherwise.
pkg/codegen/templates/echo/echo-wrappers.tmpl Adds ValueIsUnescaped: ctx.Request().URL.RawPath == "" — same correct dynamic flag as chi for echo's identical routing behaviour.
pkg/codegen/templates/gin/gin-wrappers.tmpl Sets ValueIsUnescaped: true unconditionally — correct, since gin always delivers decoded values and rejects %2F-in-param with 404.
pkg/codegen/templates/stdhttp/std-http-middleware.tmpl Sets ValueIsUnescaped: true — correct, net/http ServeMux's r.PathValue() returns decoded values; Go 1.22+ ServeMux routes on EscapedPath so %2F in a wildcard is correctly forwarded as a decoded slash.
pkg/codegen/templates/fiber/fiber-middleware.tmpl No ValueIsUnescaped added — intentional, fiber v2 delivers raw (un-decoded) values so the runtime's default unescape behaviour is correct.
internal/test/parameters/roundtrip/param_roundtrip_test.go Adds routesEncodedSlash flag to testImpl, integrates testStringEscaping sub-suite covering literal percents, spaces, unicode, encoded slashes, and the cross-term; correct per-framework flag values passed.
internal/test/parameters/roundtrip/stdhttp_escaping_test.go New file: registers only the /simpleString/{param} route to work around the digit-leading panic, correctly accesses the exported ServerInterfaceWrapper with its ErrorHandlerFunc field.
internal/test/servers/strict/chi/server.gen.go Correctly regenerated with ValueIsUnescaped: r.URL.RawPath == "" — the mismatch flagged in the previous review thread is now fixed.
internal/test/servers/strict/echo/server.gen.go Correctly regenerated with ValueIsUnescaped: ctx.Request().URL.RawPath == "" — prior hardcoded true is fixed.
internal/test/go.mod runtime dependency bumped from v1.4.2 to v1.5.0 to satisfy the new ValueIsUnescaped struct field; justified by the feature dependency.

Reviews (2): Last reviewed commit: "Tell the runtime whether routers deliver..." | Re-trigger Greptile

Comment thread internal/test/servers/strict/chi/server.gen.go
@mromaszewicz
mromaszewicz merged commit 50ecd9e into main Jul 12, 2026
28 checks passed
@mromaszewicz
mromaszewicz deleted the fix/issue-2455 branch July 12, 2026 23:59
@mromaszewicz mromaszewicz removed the notable changes Used for release notes to highlight these more highly label Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generated code needs to tell runtime whether parameters are already escaped when binding

1 participant