{"record":{"id":"213d78c65d90a93c","repo":"cloudflare/cloudflared","slug":"status-not-yet-written-before-attempting-to-hijack","errorCode":null,"errorMessage":"status not yet written before attempting to hijack connection","messagePattern":"status not yet written before attempting to hijack connection","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"connection/http2.go","lineNumber":312,"sourceCode":"}\n\nfunc (rp *http2RespWriter) WriteHeader(status int) {\n\tif rp.hijacked() {\n\t\trp.log.Warn().Msg(\"WriteHeader after hijack\")\n\t\treturn\n\t}\n\t_ = rp.WriteRespHeaders(status, rp.respHeaders)\n}\n\nfunc (rp *http2RespWriter) hijacked() bool {\n\trp.hijackedMutex.Lock()\n\tdefer rp.hijackedMutex.Unlock()\n\treturn rp.hijackedv\n}\n\nfunc (rp *http2RespWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {\n\tif !rp.statusWritten {\n\t\treturn nil, nil, fmt.Errorf(\"status not yet written before attempting to hijack connection\")\n\t}\n\t// Make sure to flush anything left in the buffer before hijacking\n\tif rp.shouldFlush {\n\t\trp.flusher.Flush()\n\t}\n\trp.hijackedMutex.Lock()\n\tdefer rp.hijackedMutex.Unlock()\n\tif rp.hijackedv {\n\t\treturn nil, nil, http.ErrHijacked\n\t}\n\trp.hijackedv = true\n\tconn := &localProxyConnection{rp}\n\t// We return the http2RespWriter here because we want to make sure that we flush after every write\n\t// otherwise the HTTP2 write buffer waits a few seconds before sending.\n\treadWriter := bufio.NewReadWriter(\n\t\tbufio.NewReader(rp),\n\t\tbufio.NewWriter(rp),\n\t)","sourceCodeStart":294,"sourceCodeEnd":330,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/connection/http2.go#L294-L330","documentation":"http2RespWriter implements http.Hijacker. Hijacking (taking over the raw connection, e.g. for websocket upgrades) is only legal after the HTTP status has been written. If Hijack is called while rp.statusWritten is false, it returns `status not yet written before attempting to hijack connection` instead of the connection.","triggerScenarios":"originProxy code (or an origin server library) calls Hijack() on the respWriter before any WriteHeader/Write occurred — typically a websocket or spliced stream handler that skips writing the 101/2xx status first.","commonSituations":"Origin libraries that hijack immediately on upgrade requests without first writing the switching-protocols response, or custom proxy code reusing cloudflared's respWriter incorrectly.","solutions":["Write the response status (e.g. http.StatusSwitchingProtocols) via WriteHeader before calling Hijack","Verify the origin's websocket handler completes its 101 response through the writer before hijacking","Ensure no early return/short-circuit skips the WriteHeader call in the proxy path","If using a third-party origin library, upgrade it to one that writes status before hijacking"],"exampleFix":"// before\nconn, rw, _ := respWriter.Hijack()\n// after\nrespWriter.WriteHeader(http.StatusSwitchingProtocols)\nconn, rw, err := respWriter.Hijack()","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func canHijack(w http.ResponseWriter) bool {\n    h, ok := w.(http.Hijacker)\n    return ok // plus ensure status already written by the caller\n}","tryCatchPattern":"conn, rw, err := respWriter.Hijack()\nif err != nil {\n    // status wasn't written yet; write the 101 then retry once\n    respWriter.WriteHeader(http.StatusSwitchingProtocols)\n    conn, rw, err = respWriter.Hijack()\n}","preventionTips":["Always WriteHeader (e.g. 101 Switching Protocols) before Hijack","Audit proxy handlers for early returns that skip WriteHeader","Use origin libraries that perform the upgrade handshake in the correct order"],"tags":["http","hijack","websocket","state"],"backgroundTag":"invalid-state-transition","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}