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]

Reply via email to