fix(dashboard): scope local-node injection lookup to the source (no cross-source leak)#3757
Conversation
buildSourceNodes injected the manager's local node by calling getNode(localNodeInfo.nodeNum) with no sourceId — a cross-source first-match that, on a multi-source deployment where two sources have heard the same nodeNum, could inject another source's row (and its position) into this source's node list. Pass source.id so the lookup is per-source; if this source hasn't stored the local node yet, it falls through to the synthesized minimal record as before. Pre-existing behavior carried over verbatim during the #3748 extraction (origin/main sourceRoutes.ts:990, 'regardless of sourceId'); surfaced in the #3748 review. The /:id/status route already scoped this lookup correctly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJPe6J5vKrcbwzt6vCdtrt
|
Claude finished @Yeraze's task —— View job PR Review
Summary: This is a clean, targeted security/correctness fix. The change is correct and the test is well-written. No blocking issues found. Code Quality
- const localNode = await databaseService.nodes.getNode(localNodeInfo.nodeNum);
+ const localNode = await databaseService.nodes.getNode(localNodeInfo.nodeNum, source.id);The The inline comment added at lines 99–104 is thorough — it explains the "why" (cross-source first-match risk), the historical origin, and the fallthrough behavior. This is good practice for a subtle concurrency/multi-source correctness fix. Test Coverage
One minor observation: the test uses Potential IssuesNone found. The fallthrough path (lines 109–118 in PerformanceNo regression. This is a strict improvement — the per-source WHERE clause can use the composite SecurityThis is a data isolation fix. Without it, on a multi-source deployment where two sources have heard the same Verdict: ✅ Approve. The fix is correct, minimal, well-commented, and well-tested. The PR description accurately describes the root cause and impact. |
Summary
Tightens a pre-existing cross-source edge case surfaced in the #3748 review.
buildSourceNodesinjects the manager's local node (when it isn't already in the source's node list) by fetching its full DB record. That lookup was callinggetNode(localNodeInfo.nodeNum)with nosourceId— andgetNodewithout asourceIddoes a cross-source first-match. On a multi-source deployment where two sources have heard the samenodeNum, this could inject another source's row (and its position) into the current source's node list.Fix: pass
source.idso the lookup is per-source. If this source hasn't stored the local node yet, it falls through to the synthesized minimal record exactly as before. The/:id/statusroute already scoped this lookup correctly (getNode(nodeNum, source.id)); this brings the nodes path in line.History
This was verbatim pre-existing behavior in the
/:id/nodesroute (origin/mainsourceRoutes.ts:990, commented "regardless of sourceId"), carried over unchanged during the #3748 extraction so that PR stayed behavior-preserving. Now that both the/:id/nodesand/:id/dashboard(+/unified/dashboard) callers sharebuildSourceNodes, this one-line fix corrects all of them.Tests
sourceRoutes.dashboard.test.ts— new case asserting the injection lookup is called with(nodeNum, source.id)and falls through to the source-scoped synthesized record.🤖 Generated with Claude Code