Skip to content

Encoded "?" (%3F) in the request path is cut off when the backend has a path #1612

Open

Description

@sergiotravassos

When a Product uses a Backend with a path, the routing rule that 3scale generates renders the decoded request path ({{uri | remove_first: '<path>'}}). If the client sends a percent-encoded ? in a path segment, APIcast treats it as the start of the query string and everything after it is dropped before the request reaches the upstream. No error is logged and the upstream gets a different resource.

%2F has a related problem: it reaches the upstream as /, so the upstream sees one more path segment than the client sent.

Version
nginx version: openresty/1.27.1.2
quay.io/3scale/apicast:latest  created 2026-10-08T08:29:38Z  (master 2cef7987)

Same result with quay.io/3scale/apicast:3scale-2.15-stable and 3scale-2.16-stable.

Steps To Reproduce

Test::Nginx case, same rule as the one 3scale generates for a Backend used with the path /REST/svc:

=== TEST 1: backend with a path keeps "%3F" and "%2F" in the upstream request path
--- configuration
{
  "services": [
    {
      "id": 42,
      "proxy": {
        "policy_chain": [
          {
            "name": "apicast.policy.routing",
            "configuration": {
              "rules": [
                {
                  "url": "http://test:$TEST_NGINX_SERVER_PORT/REST/svc",
                  "replace_path": "{{uri | remove_first: '/REST/svc'}}",
                  "condition": {
                    "operations": [
                      { "match": "path", "op": "matches", "value": "^(/REST/svc/.*|/REST/svc/?)" }
                    ]
                  }
                }
              ]
            }
          }
        ]
      }
    }
  ]
}
--- upstream
  location ~ / {
     content_by_lua_block {
       ngx.say(ngx.var.request_uri)
     }
  }
--- request eval
["GET /REST/svc/files/a%3Fb", "GET /REST/svc/files/a%3Fb?x=1", "GET /REST/svc/files/a%2Fb"]
--- response_body eval
["/REST/svc/files/a%3Fb\n", "/REST/svc/files/a%3Fb?x=1\n", "/REST/svc/files/a%2Fb\n"]
--- error_code eval
[200, 200, 200]
--- no_error_log
[error]
Current Result
Client sends Upstream receives
/REST/svc/files/a%3Fb /REST/svc/files/a
/REST/svc/files/a%3Fb?x=1 /REST/svc/files/a?x=1
/REST/svc/files/a%2Fb /REST/svc/files/a/b
Expected Result

The upstream receives the path as the client sent it: /REST/svc/files/a%3Fb, /REST/svc/files/a%3Fb?x=1 and /REST/svc/files/a%2Fb.

Additional Information

PR for the %3F case: #1613 (keeps a decoded ? coming from uri in the path instead of splitting on it). For %2F I'm not sure which way you would prefer, since the template comes from porta.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions