Skip to content

fix(server): stop writing a second response after a failed upgrade - #7563

Merged
gustavosbarreto merged 1 commit into
masterfrom
fix/websocket-upgrade-response
Oct 16, 2026
Merged

gustavosbarreto merged 1 commit into
masterfrom
fix/websocket-upgrade-response

Conversation

@geovannewashington

Copy link
Copy Markdown
Member

Summary

 HandleConnectionV1 / HandleConnectionV2 / revdial.ConnHandler
   conn, err := upgrader.Upgrade(...)      # on error, gorilla already wrote a 4xx
   if err != nil
-    write 500 with err.Error()            # ignored status, error text appended to the body
+    log the cause at Warn                 # with request-id, tenant, uid (revdial: dialer logger, uuid)
+    return nil                            # an error would make Echo write again

Revdial now resolves its dialer before the upgrade so it can log through it. A map entry that is not a *Dialer gets a 400 instead of an upgraded connection closed at once. Only *Dialer values are stored, so no caller reaches that path.

Closes shellhub-io/team#346

Evidence

  • Before: a GET without upgrade headers to /ssh/connection, /agent/connection or /ssh/revdial:

    400
    Bad Request
    websocket: the client is not using the websocket protocol: 'upgrade' token not found in 'Connection' header
    

    The server logs http: superfluous response.WriteHeader call instead of the cause.

    After: new cases in TestHandleConnectionV1StatusCodes, TestHandleConnectionV2StatusCodes and TestConnHandlerAnswersANonUpgradePickupWithOnlyTheUpgradersResponse expect status 400 and body Bad Request\n. They fail when the second write is restored and pass with this change. The full server module test suite passes.

Merge Danger

Door: two-way

A real agent sends a valid upgrade request, so its handshake does not change. No migration or wire format changes.

Blast Radius: tunnels

Only failed upgrades on the agent connection and revdial pickup paths behave differently: no second write, and a Warn log line. Any client that sends a plain GET to those paths now adds a Warn line to the logs.

@geovannewashington
geovannewashington requested a review from a team as a code owner October 16, 2026 18:07
@geovannewashington geovannewashington added kind/bug Something isn't working area/ssh labels Oct 16, 2026
@geovannewashington
geovannewashington requested a review from a team as a code owner October 16, 2026 18:07
@geovannewashington geovannewashington self-assigned this Oct 16, 2026
@github-actions

This comment has been minimized.

Comment thread pkg/revdial/revdial.go
@geovannewashington
geovannewashington force-pushed the fix/websocket-upgrade-response branch from d829cc3 to c638453 Compare October 16, 2026 18:19
@geovannewashington

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Claude finished @geovannewashington's task in 2m 43s —— View job


Code Review Complete

Reviewed 4 files on all three axes: Standards (repo rules, language conventions, tests, code smells), Spec (does it implement shellhub-io/team#346?) and Correctness (security and runtime defects). No new issues found. The code looks good as-is.

The earlier judgement call about the duplicated unknown dialer guard is fixed. One type assertion on the Load result now handles both a missing key and a non-*Dialer entry. The expanded doc comments on HandleConnectionV1, HandleConnectionV2 and ConnHandler match what each function returns. dialer.logger cannot be nil at the new log site, because the only dialers.Store call stores a *Dialer built by NewDialer. Nothing in cloud/ uses revdial or the changed handlers.

gorilla's Upgrade writes its own error response, so the handlers' second
write only appended the raw error to the body and the cause never reached
the logs. The handlers now log it and return nil, since an error would make
Echo write again. Revdial resolves its dialer before the upgrade to log
through it, so a non-Dialer entry now gets a 400 instead of an upgraded
connection that is closed at once. It ignores Load's ok because the type
assertion on the nil a missing key yields fails too. Its test reports a
failed pickup to end the dial the bad request left waiting.

Fixes: shellhub-io/team#346
@geovannewashington
geovannewashington force-pushed the fix/websocket-upgrade-response branch from c638453 to 80a4c7d Compare October 16, 2026 18:39
@gustavosbarreto
gustavosbarreto merged commit 8d4c6b9 into master Oct 16, 2026
13 checks passed
@gustavosbarreto
gustavosbarreto deleted the fix/websocket-upgrade-response branch October 16, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/pkg area/ssh kind/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants