unbridled-41 opened a new pull request, #5441:
URL: https://github.com/apache/rocketmq-dashboard/pull/5441
## Problem / Evidence
`POST /api/dlq/resend` with an explicit `targetTopic` can publish the
replayed messages into a **different cluster** than the selected instance, and
reports success.
- `provider/apache/RocketMQDLQProvider.java` `validateResendTargetTopic` —
whose javadoc claims it restricts the target to "valid, existing, non-system
topics **on the selected instance**" — only asks the NameServer-wide list:
`admin.fetchAllTopicList()` returns every topic of every cluster registered on
that endpoint. A NameServer can serve several clusters (one Studio instance per
cluster is the documented model), so a topic that exists only in a sibling
cluster passes.
- The send then reuses the instance's producer (`resendAll` →
`runtimeAdminClientResolver.executeProducer`), which sets only the instance's
`namesrvAddr`; `producer.send(message)` routes by topic name across the whole
endpoint, i.e. to whichever cluster owns the topic.
- Every sibling write path rejects exactly this:
`RocketMQAdminClientImpl.sendMessage`/`resetOffset`/`consumeMessageDirectly`
all call `ApacheWriteTargetResolver.requireTopicRoute`, whose javadoc says it
exists "so the SDK cannot fan out on its own". `RocketMQDLQProvider` referenced
neither it nor the ownership guard.
- Trigger: instance A on `ns1:9876` bound to `cluster-a`, the same
NameServer also serving `cluster-b`; `targetTopic=orders` exists only in
`cluster-b`. `POST /api/dlq/resend
{"instanceId":"A","groupName":"G","targetTopic":"orders"}` returns `SUCCESS`
and replays the dead letters into `cluster-b`'s `orders`.
## Root cause / Fix
The destination was validated by name against the whole endpoint instead of
against the cluster the dead letters came from. Resolve both routes and reject
a mismatch:
```java
Set<String> dlqClusters = clustersOf(admin.examineTopicRouteInfo(dlqTopic));
Set<String> targetClusters =
clustersOf(admin.examineTopicRouteInfo(targetTopic));
if (!dlqClusters.isEmpty() && !targetClusters.isEmpty() &&
!dlqClusters.containsAll(targetClusters)) {
throw new BusinessException(409, "targetTopic resolves to a different
cluster than " + dlqTopic + ...);
}
```
Only a resolvable route can prove the mismatch, so an unresolvable one still
falls through to the send itself (the existing existence check and its 400 are
unchanged).
## Priority & scoring
PRIORITY 61 = 影响 24 (messages silently written into another cluster while
the API and audit log say SUCCESS) + 波及 8 (the DLQ resend endpoints and the MCP
tool that forwards a model-supplied target) + 可复现 14 (deterministic given the
shared-NameServer topology) + 维护价值 15 (brings the path in line with the
confinement the sibling writes already enforce).
FIX_CONFIDENCE 76: the check mirrors
`ApacheWriteTargetResolver.requireTopicRoute`'s intent with the route data the
provider already has; wiring the full ownership guard into this provider is
deliberately left out of scope.
## Tests
```
cd server && mvn -o test -Dtest=RocketMQDLQProviderTest
```
- Red before the fix:
`resendRejectsATargetTopicThatResolvesToAnotherClusterTest` fails — the resend
proceeds into the foreign cluster instead of raising the 409.
- Green after: `Tests run: 46, Failures: 0, Errors: 0, Skipped: 0`.
- The regression stubs the DLQ topic's route in `cluster-a` and the target's
in `cluster-b` and asserts the 409 plus that no producer was ever engaged; the
existing target-validation tests (system topic, missing topic, nameserver
unreachable) keep passing unchanged.
- `mvn -o checkstyle:check` passes.
## Risk
Low. Only an explicit `targetTopic` is affected, and only when its route
resolves outside the dead-letter topic's cluster. The default destination (the
message's origin topic or the retry topic) and same-cluster resends are
untouched.
--
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]