Skip to content

fix(controller): reject query string or fragment in upstreamDefinitions URLs#2076

Open
mehara-rothila wants to merge 4 commits into
wso2:mainfrom
mehara-rothila:fix/upstream-url-query-fragment
Open

fix(controller): reject query string or fragment in upstreamDefinitions URLs#2076
mehara-rothila wants to merge 4 commits into
wso2:mainfrom
mehara-rothila:fix/upstream-url-query-fragment

Conversation

@mehara-rothila

Copy link
Copy Markdown
Contributor

Purpose

upstreamDefinitions[].upstreams[].url is intended to be host[:port] only — an upstream definition's base path is configured exclusively via upstreamDefinitions[].basePath (per #2064 and #369 (comment)).
A path in such a URL is already rejected with a 400 (#2065), but a query string (?foo=bar) or fragment (#section) is silently accepted: the upstream cluster is built from host:port only, so the query string and fragment are dropped and never reach the upstream — with no signal to the user that the value was ignored.
Fixes #2068.
Related #2064.

Goals

Reject a query string or fragment in an upstreamDefinitions URL with a 400 at deploy time, consistent with how an embedded path is already handled — giving the user a clear, unambiguous contract that these URLs are host[:port] only.

Approach

  • pkg/config/api_validator.go: in validateUpstreamDefinitions, immediately after the existing feat(controller)!: take upstreamDefinitions base path from basePath #2065 path check, reject a URL whose RawQuery is non-empty or that carries a bare ? (ForceQuery) with "URL must not include a query string", and a URL with a non-empty Fragment with "URL must not include a fragment".
  • The validator is the single deploy-time gate for upstreamDefinitions URLs; the downstream consumers (pkg/transform/restapi.go, pkg/xds/translator.go) run after validation and read only host/scheme, so no change is needed there.
  • Direct upstream.main/upstream.sandbox URLs are unchanged — they legitimately carry a base path in the URL path and are validated separately (validateUpstreamUrl); query/fragment handling for those is outside the scope of this issue.

User stories

As an API developer, when I mistakenly put a query string or fragment in an upstreamDefinitions URL, I get a clear 400 at deploy time instead of having the value silently dropped at runtime.

Documentation

N/A — no new configuration is introduced; this tightens validation of an existing field to match the documented host[:port]-only contract.

Automation tests

  • Unit tests

    Added TestValidateUpstreamDefinitions_URLWithQueryRejected and TestValidateUpstreamDefinitions_URLWithFragmentRejected, mirroring the existing TestValidateUpstreamDefinitions_URLWithPathRejected. go test ./pkg/config/ passes with no regression.

  • Integration tests

    N/A — this is deploy-time input validation with no runtime behavior change.

Security checks

Samples

N/A

Related PRs

#2065 (take upstreamDefinitions base path from basePath) introduced the path rejection this builds on; #2059 (dynamic-endpoint sandbox routing) is adjacent work on the same area.

Test environment

Go (multi-module workspace); go test ./pkg/config/.

@CLAassistant

CLAassistant commented Jun 2, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9aac92e9-2972-4b66-ac3b-ae31ef2b3eab

📥 Commits

Reviewing files that changed from the base of the PR and between d5bc0b5 and 31f1286.

📒 Files selected for processing (3)
  • gateway/gateway-controller/pkg/config/api_validator.go
  • gateway/gateway-controller/pkg/config/validator_test.go
  • gateway/it/features/api_deploy.feature
🚧 Files skipped from review as they are similar to previous changes (2)
  • gateway/gateway-controller/pkg/config/validator_test.go
  • gateway/gateway-controller/pkg/config/api_validator.go

📝 Walkthrough

Summary

This PR adds validation to enforce that upstreamDefinitions[].upstreams[].url accepts only host[:port] format by rejecting URLs containing query strings or fragments at deploy time.

Changes

pkg/config/api_validator.go

  • Extended validateUpstreamDefinitions to reject URLs with non-empty RawQuery/ForceQuery (error: "URL must not include a query string; only host[:port] is used")
  • Added validation to reject URLs with non-empty Fragment (error: "URL must not include a fragment; only host[:port] is used")
  • These checks complement the existing path rejection validation

pkg/config/validator_test.go

  • Added TestValidateUpstreamDefinitions_URLWithQueryRejected – validates rejection of URLs with query parameters (?foo=bar)
  • Added TestValidateUpstreamDefinitions_URLWithBareQueryRejected – validates rejection of bare query separator (?)
  • Added TestValidateUpstreamDefinitions_URLWithFragmentRejected – validates rejection of URL fragments (#section)

it/features/api_deploy.feature

  • Added integration test scenario verifying API deployment fails when upstreamDefinitions upstream URL contains a query string, with proper error response indicating validation failure

Impact

Users deploying APIs with query strings or fragments in upstreamDefinitions URLs now receive a clear 400-level validation error at deploy time rather than having these components silently dropped at runtime. This aligns with the existing behavior of rejecting paths and reinforces that upstreamDefinitions[].basePath is the exclusive place for base path configuration.

Walkthrough

This pull request updates validateUpstreamDefinitions to reject upstream target URLs that include query strings (including bare ?/ForceQuery) or fragments. The validator parses each upstream URL and returns field-specific ValidationError entries when a query string or fragment is detected. Three unit tests verify rejection for URLs with a query string, a bare query marker, and a fragment, each targeting spec.upstreamDefinitions[0].upstreams[0].url and validating error message content. A feature test scenario validates the end-to-end API deployment flow, confirming that deployments with query strings in upstream URLs are rejected with appropriate error messages.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: rejecting query strings and fragments in upstreamDefinitions URLs, which is the primary objective of the PR.
Description check ✅ Passed The PR description comprehensively covers all required template sections: Purpose (with issue links), Goals, Approach, User stories, Documentation, Automation tests, Security checks, and Related PRs. All critical information is present and well-detailed.
Linked Issues check ✅ Passed The PR changes directly address issue #2068 by implementing validation to reject query strings and fragments in upstreamDefinitions URLs at deploy time with appropriate error messages, matching the desired resolution outlined in the issue.
Out of Scope Changes check ✅ Passed All changes are within scope: validation logic in api_validator.go, corresponding unit tests in validator_test.go, and an integration test in api_deploy.feature. Changes focus exclusively on the stated objective of rejecting query strings and fragments in upstreamDefinitions URLs.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mehara-rothila

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
gateway/gateway-controller/pkg/config/validator_test.go (1)

623-643: ⚡ Quick win

Add coverage for the trailing ? case.

Line 624 covers a non-empty query, but the validator also rejects parsedURL.ForceQuery for URLs like http://backend-1:8080?. Please add that case so the full new branch stays protected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gateway/gateway-controller/pkg/config/validator_test.go` around lines 623 -
643, TestValidateUpstreamDefinitions_URLWithQueryRejected only covers URLs with
a non-empty query; add a second assertion case that passes an upstream URL
ending with a trailing '?' (e.g., "http://backend-1:8080?") to exercise the
parsedURL.ForceQuery branch. Update the test (within
TestValidateUpstreamDefinitions_URLWithQueryRejected or a small companion test)
to call validator.validateUpstreamDefinitions with that URL and assert the
returned error references "spec.upstreamDefinitions[0].upstreams[0].url" and
contains "query string" (or the same message used for ForceQuery), ensuring the
validator.validateUpstreamDefinitions/parsedURL.ForceQuery behavior is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@gateway/gateway-controller/pkg/config/validator_test.go`:
- Around line 623-643: TestValidateUpstreamDefinitions_URLWithQueryRejected only
covers URLs with a non-empty query; add a second assertion case that passes an
upstream URL ending with a trailing '?' (e.g., "http://backend-1:8080?") to
exercise the parsedURL.ForceQuery branch. Update the test (within
TestValidateUpstreamDefinitions_URLWithQueryRejected or a small companion test)
to call validator.validateUpstreamDefinitions with that URL and assert the
returned error references "spec.upstreamDefinitions[0].upstreams[0].url" and
contains "query string" (or the same message used for ForceQuery), ensuring the
validator.validateUpstreamDefinitions/parsedURL.ForceQuery behavior is covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f311aa0a-4685-4e02-aafa-3e039c750a17

📥 Commits

Reviewing files that changed from the base of the PR and between 08d3f58 and 5d4499b.

📒 Files selected for processing (2)
  • gateway/gateway-controller/pkg/config/api_validator.go
  • gateway/gateway-controller/pkg/config/validator_test.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds stricter validation for upstreamDefinitions[].upstreams[].url to reject query strings and fragments (which would otherwise be silently ignored), along with new unit tests covering these cases.

Changes:

  • Reject upstream URLs that include a query string (including a bare ?).
  • Reject upstream URLs that include a fragment (#...).
  • Add unit tests for query and fragment rejection.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

File Description
gateway/gateway-controller/pkg/config/validator_test.go Adds tests ensuring upstream URLs with query/fragment are rejected.
gateway/gateway-controller/pkg/config/api_validator.go Adds validation that flags query strings and fragments as invalid in upstream URLs.

Comment thread gateway/gateway-controller/pkg/config/validator_test.go Outdated
Comment thread gateway/gateway-controller/pkg/config/validator_test.go Outdated
Comment thread gateway/gateway-controller/pkg/config/validator_test.go Outdated
Comment thread gateway/gateway-controller/pkg/config/api_validator.go Outdated
Comment thread gateway/gateway-controller/pkg/config/validator_test.go
Comment thread gateway/gateway-controller/pkg/config/validator_test.go
Comment thread gateway/gateway-controller/pkg/config/validator_test.go
@renuka-fernando renuka-fernando changed the base branch from main to feature/operation-level-ep June 2, 2026 14:09
@mehara-rothila mehara-rothila changed the base branch from feature/operation-level-ep to main June 16, 2026 12:38
@mehara-rothila

Copy link
Copy Markdown
Contributor Author

@coderabbitai Can you do a final review and tell me if this PR is complete and ready to merge, or if anything is missing?

Context , this PR rejects a query string or fragment in upstreamDefinitions[].upstreams[].url (they get dropped when the cluster is built, so we fail fast at deploy with a 400 instead of silently ignoring them). Since your earlier reviews I:

  • reworded the two validation messages to state the rule: URL must not include a query string/fragment; only host[:port] is used
  • added an integration test in api_deploy.feature that deploys a query-string upstream URL and asserts the 400 plus the message
  • the base was moved from feature/operation-level-ep to main (diff is still only the two validator files + the IT)

Please confirm:

  1. Validation logic , does it fully cover query (RawQuery and the bare-? ForceQuery) and fragment, no edge cases missed?
  2. Reworded messages , clear and consistent with the existing path-rejection message?
  3. The new integration test , correct and enough, or should it also cover the fragment / bare-? case end to end?
  4. Anything else blocking merge?

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the validator file
fd -e go api_validator.go --exec cat {}

echo "=== SEPARATOR ==="

# Read the test file
fd -e go validator_test.go --exec cat {}

echo "=== SEPARATOR2 ==="

# Read the integration test feature file
fd -e feature api_deploy.feature --exec cat {}

Length of output: 176091



@mehara-rothila Here is the final assessment against each of your four questions:


1. Validation logic — fully covered?

Yes. The three cases are all handled correctly in validateUpstreamDefinitions:

Input Field checked Example
?foo=bar parsedURL.RawQuery != "" http://host:9080?foo=bar
? (bare) parsedURL.ForceQuery http://host:9080?
#section parsedURL.Fragment != "" http://host:9080#section

One very minor edge case: a bare # with no text after it (e.g. http://host#) results in Fragment == "" and RawFragment == "" in Go's url.Parse, so it slips through. This is semantically equivalent to no fragment and is an extremely unlikely input — not blocking for this PR, but worth tracking as a follow-up if desired.


2. Error messages — clear and consistent?

Yes. The two new messages are clear:

  • "URL must not include a query string; only host[:port] is used"
  • "URL must not include a fragment; only host[:port] is used"

They differ slightly in style from the existing path message ("...set the base path in upstreamDefinitions[].basePath instead"), which points to an alternative. The new messages instead state the contract directly. That is appropriate here because there is no alternative field for query strings or fragments — they are simply disallowed. No change needed.


3. Integration test — sufficient?

Sufficient as-is. The api_deploy.feature scenario covers the primary case end-to-end (deploy with a query-string URL, assert 400 + message text). Fragment and bare-? are covered by unit tests (TestValidateUpstreamDefinitions_URLWithFragmentRejected, TestValidateUpstreamDefinitions_URLWithBareQueryRejected) which exercise the same code path. Adding dedicated IT scenarios for those would provide minimal additional confidence at non-trivial cost. It is reasonable to omit them.


4. Anything else blocking merge?

Nothing blocking. A few minor observations for completeness:

  • The bare-# edge case noted above is not blocking; open a follow-up issue if you want to track it.
  • Validation correctly does not early-return after a query/fragment error, so multiple errors per entry can be collected — this is the right behaviour.
  • No changes are needed in pkg/transform/restapi.go or pkg/xds/translator.go — confirmed.

The PR is complete and ready to merge.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

upstreamDefinitions URLs do not support query strings or fragments

3 participants