Deep Review: 20260330-155444-pr-198
| Date | 2026-03-30 15:54 |
| Repo | rancher-sandbox/rancher-desktop-daemon |
| Round | 2 |
| Author | @mook-as |
| PR | #198 — mock controller: Add a mock logs endpoint |
| Branch | mook-as/container-api/mock-logs |
| Commits | 1b7060e mock controller: Add a mock logs endpoint |
| Reviewers | Claude Opus 4.6, Codex GPT 5.4, Gemini 3.1 Pro |
| Verdict | Merge with fixes — WebSocket upgrader rejects browser clients; error responses mask backend failures |
| Wall-clock time | 13 min 29 s |
Executive Summary ¶
This PR adds a mock WebSocket logs endpoint (/passthrough/mock/logs/{container}) that streams container labels as log lines, intended for UI development. It wires the endpoint into the mock controller's passthrough discovery, extracts a reusable curl_has_websocket_support helper, and adds BATS tests covering valid containers, invalid IDs, and nonexistent containers. The main concern: the WebSocket upgrader's default origin check rejects browser connections — the primary use case this endpoint serves.
Critical Issues ¶
None.
Important Issues ¶
The zero-value Upgrader uses gorilla/websocket's default checkSameOrigin, which rejects requests whose Origin header does not match Host. Browser WebSocket requests always send Origin. When the UI runs on a separate dev server (e.g., Vite on port 5173) or in Electron with a custom origin, the origins diverge and the upgrade fails with 403. The BATS tests pass because curl omits the Origin header, so checkSameOrigin returns true unconditionally.
log.V(5).Info("Failed to get container", "error", err)
http.Error(w, "Failed to get container", http.StatusNotFound)
return
}
upgrader := websocket.Upgrader{}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.V(5).Info("Failed to upgrade to WebSocket", "error", err)
return
}
The existing demo controller at pkg/controllers/app/demo/controller.go:90-92 explicitly bypasses this check.
func (c *controller) handleWebSocket(w http.ResponseWriter, r *http.Request) {
log := ctrl.LoggerFrom(r.Context())
upgrader := &websocket.Upgrader{
CheckOrigin: func(_ *http.Request) bool { return true },
}
conn, err := upgrader.Upgrade(w, r, nil)
Confirmed via git blame: introduced in commit 1b7060e.
Fix: Match the demo controller's pattern.
- upgrader := websocket.Upgrader{}
+ upgrader := websocket.Upgrader{
+ CheckOrigin: func(_ *http.Request) bool { return true },
+ }
Suggestions ¶
Any error from GetClient().Get() — not-found, kine database issue, context cancellation — returns HTTP 404. During UI development, this masks real backend problems as "container not found." Distinguishing not-found from internal errors improves debuggability.
var container v1alpha1.Container
err := c.mgr.GetClient().Get(r.Context(), types.NamespacedName{
Namespace: apiNamespace,
Name: containerID,
}, &container)
if err != nil {
log.V(5).Info("Failed to get container", "error", err)
http.Error(w, "Failed to get container", http.StatusNotFound)
return
}
Fix: Use apierrors.IsNotFound to differentiate (already imported in mock_controller.go).
+ if apierrors.IsNotFound(err) {
+ log.V(5).Info("Container not found", "container", containerID)
+ http.Error(w, "Container not found", http.StatusNotFound)
+ return
+ }
if err != nil {
- log.V(5).Info("Failed to get container", "error", err)
- http.Error(w, "Failed to get container", http.StatusNotFound)
+ log.V(5).Info("Failed to get container", "container", containerID, "error", err)
+ http.Error(w, "Failed to get container", http.StatusInternalServerError)
return
}
strings.Cut extracts the first path segment as containerID and discards the rest. A request to /passthrough/mock/logs/1234/extra/stuff succeeds with container ID 1234. While harmless for a mock endpoint, it can mask malformed frontend requests during development.
func (c *controller) handleLogs(w http.ResponseWriter, r *http.Request) {
log := ctrl.LoggerFrom(r.Context())
containerID, _, _ := strings.Cut(strings.TrimLeft(r.URL.Path, "/"), "/")
log.Info("Handling logs request", "container", containerID)
if !containerIDValidator.MatchString(containerID) {
log.V(5).Info("Invalid container ID", "container", containerID)
http.Error(w, "Invalid container ID", http.StatusBadRequest)
Fix: Check the after return value and reject requests with extra segments.
- containerID, _, _ := strings.Cut(strings.TrimLeft(r.URL.Path, "/"), "/")
+ containerID, after, _ := strings.Cut(strings.TrimLeft(r.URL.Path, "/"), "/")
+ if after != "" {
+ http.Error(w, "Invalid path", http.StatusBadRequest)
+ return
+ }
The deferred conn.Close() runs immediately after the close-frame write without waiting for the client to echo the close frame. Browser clients may see 1006 Abnormal Closure instead of the clean 1000 Normal Closure. Acceptable for a mock endpoint, but a brief read loop after sending the close message would produce cleaner client-side behavior.
defer conn.Close()
defer func() {
message := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "Closing connection")
err := conn.WriteControl(websocket.CloseMessage, message, time.Now().Add(time.Second))
if err != nil {
log.V(5).Info("Failed to close WebSocket", "error", err)
}
}()
Design Observations ¶
Strengths
- Reuses the
PassthroughControllerinterface and discovery ConfigMap pattern from the demo controller. Endpoint registration is clean and extensible. Claude Codex - The WebSocket close sequence (lines 57-63) correctly uses LIFO defer ordering: send close message first, then close the connection. Claude
- Extracting
curl_has_websocket_support()intocommands.basheliminates duplication across both WebSocket passthrough tests. Claude - Test structure covers three distinct cases: valid container, invalid ID format, and nonexistent container. Claude
Testing Assessment ¶
Coverage is adequate for a mock endpoint:
- No test exercises the endpoint with a browser-style
Originheader. The BATS tests usecurl, which omitsOrigin, so they miss the failure described in Important Finding 1. - Container with nil labels (
Status.Labelsis nil) is untested, but safe —maps.Keys(nil)returns an empty iterator. - Empty container ID is implicitly covered by the hex-character regex requiring at least one character (
+).
Documentation Assessment ¶
No documentation changes needed. This is a mock-only endpoint for internal development.
Agent Performance Retro ¶
Claude
- Duration: 8 min 34 s
- Found 2 issues: Important 1, Suggestion 1
- Unique contributions: detailed analysis of when the
Origincheck would and would not fail (passthrough preservingHostheader), thorough testing assessment with edge case enumeration (nil labels, empty ID) - Accuracy: no false positives
- Depth: traced into demo controller for pattern comparison, examined defer ordering
- Coverage: reviewed all 5 files, all accounted for in coverage summary
- Signal-to-noise: high — every finding is actionable
Codex
- Duration: 2 min 36 s
- Found 1 issue: Important 1
- Unique contributions: none — its single finding overlaps with the other two agents
- Accuracy: no false positives
- Depth: traced into
app/demo/controller.gofor comparison, rangit blame - Coverage: reviewed all 5 files, but missed the error masking and path segment issues
- Coverage misses:
logs_passthrough.golines 44-47 (error masking) flagged by both other agents - Signal-to-noise: clean but shallow
Gemini
- Duration: 5 min 16 s
- Found 4 issues: Important 2, Suggestion 2
- Unique contributions: trailing path segments (Suggestion 2), WebSocket close frame timing (Suggestion 3)
- Accuracy: no false positives
- Depth: examined error propagation, WebSocket protocol behavior, and path parsing
- Coverage: reviewed all 5 files, all accounted for
- Signal-to-noise: high — all findings are actionable, no noise
Summary
| Metric | Claude Opus 4.6 | Codex GPT 5.4 | Gemini 3.1 Pro |
|---|---|---|---|
| Duration | 8:34 | 2:36 | 5:16 |
| Critical | 0 | 0 | 0 |
| Important | 1 | 1 | 2 |
| Suggestion | 1 | 0 | 2 |
| Design observations | 4 | 1 | 0 |
| False positives | 0 | 0 | 0 |
| Unique insights | 0 | 0 | 2 |
| Files reviewed | 5 | 5 | 5 |
| Coverage misses | 0 | 1 | 0 |
Gemini delivered the most comprehensive review, finding all four consolidated issues including two unique suggestions. Claude provided strong design observations and testing assessment. Codex was fast but shallow, contributing no unique findings. All three agents converged on the highest-value finding (WebSocket origin check), validating the multi-agent approach.