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]
