tju-yxq opened a new issue, #6169:
URL: https://github.com/apache/rocketmq-dashboard/issues/6169

   > Resubmission of #5165 — the original report was closed by the repo's 7-day 
stale bot for inactivity, not because it was fixed or rejected; the problem is 
still reproducible on the current `rocketmq-studio` head. An updated PR 
carrying the fix is being filed against this report.
   
   ## Problem
   
   Every server-side URL validation path accepts URLs that embed credentials as 
user-info (`http://user:password@host`). A data source saved with such a URL 
passes the shared SSRF guard, is persisted with the credential inside, and is 
then returned — credential included — by the data source list API that every 
authenticated operator, not just administrators, can read.
   
   ## What did you do (steps to reproduce)?
   
   1. Sign in as an administrator and open **Settings → Metrics data sources** 
(创建数据源).
   2. Create a data source with the URL 
`http://prometheus:[email protected]:9090/metrics` — basic-auth-in-URL is a 
common Prometheus deployment pattern, and the guard accepts it (the host 
`10.1.2.3` is a legitimate internal address). Click save. The save succeeds.
   3. Sign in (or switch sessions) as a non-admin operator — the reader role.
   4. Call `GET /api/settings/datasources` (or simply open the Metrics tab, 
which loads the data source dropdown on first paint).
   
   ## What did you expect to see?
   
   The URL to be rejected at save time with a clear message directing 
credentials to the dedicated auth field, because the URL is persisted and 
echoed back by reader-visible APIs.
   
   ## What did you see instead?
   
   The save succeeds and the list returns the data source with `url: 
"http://prometheus:[email protected]:9090/metrics"` — the Prometheus password is 
now readable by every authenticated operator. The same gap exists for the LLM 
base URL (`http://key:[email protected]/v1` passes 
`LlmConfigService.isValidApiBase`) and for webhook URLs, which run through the 
same shared guard at send time.
   
   ## Root cause
   
   
`server/src/main/java/org/apache/rocketmq/studio/common/util/UrlHostGuard.java` 
validates the scheme, the host presence and the resolved addresses, but never 
inspects `URI.getUserInfo()`:
   
   - `UrlHostGuard.java:69-80` (`check` at `master@0228dad5`) — scheme check, 
host check, `isAllowedHost`; no user-info check;
   - The guard is the single choke point for data source URLs 
(`SettingsService.validateDataSourceUrl`, line 268), the LLM base URL 
(`SettingsService.validateLlmBaseUrl`, line 175) and webhook sends 
(`NotificationOutboxService.sendWebhook`, line 405);
   - `LlmConfigService.isValidApiBase` (lines 378-393) parses its own URI and 
only asks `UrlHostGuard.isAllowedHost(uri.getHost(), true)` — user-info 
invisible there too;
   - The read side makes it an actual disclosure, not just persistence hygiene: 
`AuthInterceptor.requiresAdmin` keeps every GET open to readers except the 
admin-only list (`isAdminOnlyGetPath`), and `GET /api/settings/datasources` is 
**not** on that list, so the URL with the embedded credential is served to any 
authenticated operator.
   
   The codebase already models credentials properly — `DataSourceVO.auth` for 
data sources, `apiKey` for the LLM, `dingtalkSigningSecret` for DingTalk — so a 
userinfo URL is never the intended path; it is simply not refused anywhere.
   
   ## Expected behavior
   
   1. `UrlHostGuard.check` rejects any URL whose user-info is present and 
non-blank, with a message that names the dedicated credential fields (data 
source `auth`, LLM `apiKey`). The check runs before host resolution, so it also 
fails fast.
   2. `LlmConfigService.isValidApiBase` rejects base URLs with user-info the 
same way.
   3. Webhook URLs are covered by the same guard change (send-time validation).
   4. Existing behaviour for clean URLs is unchanged, including private 
site-local hosts and loopback for local LLM gateways.
   
   ## Acceptance criteria
   
   - [ ] A regression test that creates a data source with 
`http://user:pass@host` and asserts a 400 naming the credential rule — this 
test fails on the unfixed source (the save currently succeeds).
   - [ ] Unit tests that `UrlHostGuard.check` rejects 
`http://user:[email protected]/metrics` and `http://[email protected]/metrics` 
(user-info without a password included), and still accepts the same URL without 
user-info.
   - [ ] A test that saving an LLM config with 
`http://key:[email protected]/v1` is rejected with 
`llm.config.invalid_api_base`.
   - [ ] The pre-existing SSRF guard tests (loopback, metadata, unique-local 
IPv6, multicast) keep passing.
   
   ## Environment
   
   - Branch: `master` @ `0228dad5` (2026-09-29)
   - Files involved: 
`server/src/main/java/org/apache/rocketmq/studio/common/util/UrlHostGuard.java`,
 `server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigService.java`
   - Read path: `GET /api/settings/datasources` is reader-accessible per 
`AuthInterceptor.isAdminOnlyGetPath`
   
   ## Other information
   
   The SSRF guard was introduced by #1673 and hardened by #1944 (all DNS 
answers), #2054/#2038 (LLM base URLs), #2304 (IPv6 unique-local) and #4681 
(data-source test path); authenticated data sources gained the dedicated `auth` 
field in #1681 and credential fields are redacted from `toString` by #3373. 
None of these look at user-info, which is how a credential in the URL stays 
both persisted and reader-visible while the dedicated fields stay protected.
   
   ## Duplicate check
   
   Searched open and closed issues/PRs for `user-info`/`userinfo` + URL, 
`embedded credentials`, `credentials in the URL`, `user:pass`, URL + password + 
data source, and `UrlHostGuard`. The closest matches are #3373 (redacts 
credential *fields* from `toString`, not URLs) and #4681 (open; routes the 
data-source *test* path through the shared guard) — neither rejects credentials 
embedded in the URL itself. No existing issue tracks this defect.
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to