{"record":{"id":"eb0d6cc087f68ebc","repo":"juanfont/headscale","slug":"invalid-auth-id-eb0d6c","errorCode":null,"errorMessage":"Invalid auth_id","messagePattern":"Invalid auth_id","errorType":"http","errorClass":"HTTPError","httpStatus":400,"severity":"warning","filePath":"hscontrol/noise.go","lineNumber":589,"sourceCode":"}\n\n// sshActionFollowUp handles follow-up requests where the client\n// provides an auth_id. It blocks until the auth session resolves or\n// the request context is cancelled (e.g. the client disconnects).\nfunc (ns *noiseServer) sshActionFollowUp(\n\tctx context.Context,\n\treqLog zerolog.Logger,\n\taction *tailcfg.SSHAction,\n\tauthIDStr string,\n\tsrcNodeID, dstNodeID types.NodeID,\n\tcheckFound bool,\n) (*tailcfg.SSHAction, error) {\n\tauthID, err := types.AuthIDFromString(authIDStr)\n\tif err != nil {\n\t\treturn nil, NewHTTPError(\n\t\t\thttp.StatusBadRequest,\n\t\t\t\"Invalid auth_id\",\n\t\t\tfmt.Errorf(\"parsing auth_id: %w\", err),\n\t\t)\n\t}\n\n\treqLog = reqLog.With().Str(\"auth_id\", authID.String()).Logger()\n\n\tauth, ok := ns.headscale.state.GetAuthCacheEntry(authID)\n\tif !ok {\n\t\t// The session is gone (expired, evicted, or lost on a control-plane\n\t\t// restart). A bare error dead-ends the client: it keeps polling this\n\t\t// now-defunct auth_id until the SSH connection times out. Re-delegate\n\t\t// so a still-required check can complete instead.\n\t\tif checkFound {\n\t\t\treqLog.Info().Caller().\n\t\t\t\tMsg(\"SSH check auth session missing; re-delegating\")\n\n\t\t\treturn ns.sshActionHoldAndDelegate(\n\t\t\t\treqLog, action, srcNodeID, dstNodeID,\n\t\t\t)","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/noise.go#L571-L607","documentation":"Returned by the SSH action follow-up handler when the auth_id string fails types.AuthIDFromString validation. The auth_id arrives as a URL parameter on the hold/check URL; anything that is not a valid opaque AuthID token (truncated, edited, injected) is rejected with 400.","triggerScenarios":"The user alters or truncates the SSH check URL in the browser; URL-encoding corruption through a reverse proxy; injection attempts against the follow-up endpoint.","commonSituations":"Browser or chat client wrapping/copying the URL badly; hand-typed URLs; a proxy re-encoding query parameters.","solutions":["Open the exact SSH check URL the control plane issued, unmodified","Copy URLs whole (quoting in shells) to avoid truncation","Check reverse proxy handling of query string encoding"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isValidAuthIDParam(s string) bool {\n    _, err := types.AuthIDFromString(s)\n    return err == nil\n}","tryCatchPattern":null,"preventionTips":["Never hand-edit auth_id values in URLs","Use clickable links or full clipboard copies of issued URLs"],"tags":["ssh","auth","url-parameter","validation"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}