{"record":{"id":"5c6d48164560f72f","repo":"ginuerzh/gost","slug":"read-not-supported-5c6d48","errorCode":null,"errorMessage":"read not supported","messagePattern":"read not supported","errorType":"exception","errorClass":"net.OpError","httpStatus":null,"severity":"error","filePath":"ssh.go","lineNumber":906,"sourceCode":"\treturn func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {\n\t\tif keys[string(pubKey.Marshal())] {\n\t\t\treturn &ssh.Permissions{\n\t\t\t\t// Record the public key used for authentication.\n\t\t\t\tExtensions: map[string]string{\n\t\t\t\t\t\"pubkey-fp\": ssh.FingerprintSHA256(pubKey),\n\t\t\t\t},\n\t\t\t}, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"unknown public key for %q\", c.User())\n\t}\n}\n\ntype sshNopConn struct {\n\tsession *sshSession\n}\n\nfunc (c *sshNopConn) Read(b []byte) (n int, err error) {\n\treturn 0, &net.OpError{Op: \"read\", Net: \"ssh\", Source: nil, Addr: nil, Err: errors.New(\"read not supported\")}\n}\n\nfunc (c *sshNopConn) Write(b []byte) (n int, err error) {\n\treturn 0, &net.OpError{Op: \"write\", Net: \"ssh\", Source: nil, Addr: nil, Err: errors.New(\"write not supported\")}\n}\n\nfunc (c *sshNopConn) Close() error {\n\treturn nil\n}\n\nfunc (c *sshNopConn) LocalAddr() net.Addr {\n\treturn &net.TCPAddr{\n\t\tIP:   net.IPv4zero,\n\t\tPort: 0,\n\t}\n}\n\nfunc (c *sshNopConn) RemoteAddr() net.Addr {","sourceCodeStart":888,"sourceCodeEnd":924,"githubUrl":"https://github.com/ginuerzh/gost/blob/a33fdbf4c98034f4bfeeaea9868909822b9c526d/ssh.go#L888-L924","documentation":"sshNopConn is a write-only SSH connection wrapper used internally by the tunnel library (e.g. for connector/dialer plumbing). Its Read method is deliberately unimplemented and returns a *net.OpError wrapping \"read not supported\" with Net \"ssh\". If any code path tries to read from this nop connection, it means the connection is being used in the wrong direction.","triggerScenarios":"Calling Read on a net.Conn that is actually a *sshNopConn, e.g. wrapping the sshSession's connection in an API that requires a bidirectional net.Conn and then attempting io.Read / io.Copy(dst, conn) from it.","commonSituations":"Passing the nop conn to libraries that expect a full-duplex net.Conn (TLS handshake, HTTP client transports, io.Copy loops); version changes where a code path started reading on a connection that was only meant for writing.","solutions":["Locate the call site reading from the sshNopConn and use the real underlying ssh channel/connection for reads instead","Ensure the nop conn is only used where a write-only conn is expected; swap in sshConn (channel-backed) if bidirectional I/O is needed","If you own the caller, check for net.OpError with Op \"read\" and Net \"ssh\" early and fail fast with a clearer message"],"exampleFix":"// before\nio.Copy(stdout, nopConn) // read not supported\n// after\nio.Copy(stdout, realSshConn) // use the channel-backed sshConn","handlingStrategy":"type-guard","validationCode":"func isSSHNopConn(c net.Conn) bool { _, ok := c.(*sshNopConn); return ok }\n// before reading:\nif isSSHNopConn(conn) { return errors.New(\"read unsupported on this ssh conn\") }","typeGuard":"func asReaderConn(c net.Conn) (io.Reader, bool) {\n    if _, nop := c.(*sshNopConn); nop { return nil, false }\n    return c, true\n}","tryCatchPattern":"n, err := conn.Read(buf)\nif err != nil {\n    var opErr *net.OpError\n    if errors.As(err, &opErr) && opErr.Net == \"ssh\" && opErr.Op == \"read\" {\n        return fmt.Errorf(\"ssh conn is write-only: %w\", err)\n    }\n    return err\n}","preventionTips":["Never pass sshNopConn to APIs requiring a full-duplex net.Conn","Type-assert the conn before wiring it into read paths","Prefer the channel-backed sshConn for bidirectional I/O"],"tags":["ssh","net-op-error","unsupported-operation","read"],"backgroundTag":"read-not-supported","analyzedSha":"a33fdbf4c98034f4bfeeaea9868909822b9c526d","analyzedAt":"2026-09-02T22:15:54.506Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}