{"record":{"id":"ceabc59ca8126aeb","repo":"gravitational/teleport","slug":"unexpected-message-type","errorCode":null,"errorMessage":"unexpected message type","messagePattern":"unexpected message type","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"info","filePath":"lib/srv/desktop/tdp/mfa.go","lineNumber":31,"sourceCode":"//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\npackage tdp\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"log/slog\"\n\n\t\"github.com/gravitational/trace\"\n\n\t\"github.com/gravitational/teleport/api/client/proto\"\n\t\"github.com/gravitational/teleport/api/mfa\"\n)\n\nvar (\n\tErrUnexpectedTDPMessageType = errors.New(\"unexpected message type\")\n)\n\n// convertChallenge converts an MFA challenge to a Message. Returns\n// a non-nil error if the conversion fails\ntype convertChallenge func(*proto.MFAAuthenticateChallenge) (Message, error)\n\n// asMFAResponse returns:\n//   - ErrUnexpectedTDPMessageType if a valid messages was received but was not an MFA message.\n//   - Any other non-nil error if there was an error interpreting the message.\n//   - nil if a valid, non-nil MFA messages was found.\ntype asMFAResponse func(Message) (*proto.MFAAuthenticateResponse, error)\n\n// NewMfaPrompt constructs a function that reads, encodes, and sends an MFA challenge to the client,\n// then waits for the corresponding MFA response message. It caches any non-MFA messages received so\n// that they may be forwarded to the server later on.\nfunc NewMFAPrompt(rw MessageReadWriter, asResponse asMFAResponse, toMessage convertChallenge, withheld *[]Message, log *slog.Logger) mfa.PromptFunc {\n\treturn func(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) {\n\t\tchallengeMsg, err := toMessage(chal)","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/gravitational/teleport/blob/1283425b60ec5f60d509ba4c791183d452923ff7/lib/srv/desktop/tdp/mfa.go#L13-L49","documentation":"Sentinel error returned when a TDP message read while waiting for MFA authentication messages is not one of the expected MFA types. Callers treat it as a signal to skip the message and keep reading, not a fatal failure.","triggerScenarios":"A non-MFA TDP message (e.g. mouse move, screen data) arrives while ReadMFAAuthenticateResponse-style code is waiting for an MFA challenge/response; also returned by legacy mfa.go converters in their default switch case.","commonSituations":"During RDP login with MFA, interleaved normal streaming messages arrive between MFA frames; protocol version mismatch causing unexpected message types.","solutions":["No action needed in MFA read loops — match with errors.Is(err, ErrUnexpectedTDPMessageType) and continue reading","If seen from legacy protocol/legacy/mfa.go default case unexpectedly, verify the peer's TDP protocol version","Check for protocol desync (message stream corrupted or wrong wire format) if this error repeats abnormally"],"exampleFix":"// before\nresp, err := asResponse(msg)\nif err != nil { return nil, err }\n// after\nresp, err := asResponse(msg)\nif errors.Is(err, tdp.ErrUnexpectedTDPMessageType) { continue }\nif err != nil { return nil, err }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"resp, err := asResponse(msg)\nif errors.Is(err, tdp.ErrUnexpectedTDPMessageType) {\n    continue // skip non-MFA message\n}\nif err != nil { return nil, trace.Wrap(err) }","preventionTips":["Always use errors.Is against the sentinel, never string compare","Keep read loops tolerant of interleaved non-MFA messages","Track unexpected types in debug logs to spot protocol desync"],"tags":["tdp","mfa","protocol"],"backgroundTag":"unexpected-message-type","analyzedSha":"1283425b60ec5f60d509ba4c791183d452923ff7","analyzedAt":"2026-09-02T04:06:41.601Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}