{"record":{"id":"3d4d22f70d050f66","repo":"gravitational/teleport","slug":"prompt-returned-invalid-credential-v","errorCode":null,"errorMessage":"prompt returned invalid credential: %#v","messagePattern":"prompt returned invalid credential: %#v","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/auth/webauthncli/fido2.go","lineNumber":443,"sourceCode":"\tcredToAssertion := make(map[*CredentialInfo]*libfido2.Assertion)\n\tfor i, assertion := range assertions {\n\t\tcred := &CredentialInfo{\n\t\t\tID: assertion.CredentialID,\n\t\t\tUser: UserInfo{\n\t\t\t\tUserHandle: assertion.User.ID,\n\t\t\t\tName:       assertion.User.Name,\n\t\t\t},\n\t\t}\n\t\tcredToAssertion[cred] = assertion\n\t\tcreds[i] = cred\n\t}\n\tchosen, err := prompt.PromptCredential(creds)\n\tif err != nil {\n\t\treturn nil, trace.Wrap(err)\n\t}\n\tassertion, ok := credToAssertion[chosen]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"prompt returned invalid credential: %#v\", chosen)\n\t}\n\treturn assertion, nil\n}\n\n// fido2Register implements FIDO2Register.\nfunc fido2Register(\n\tctx context.Context,\n\torigin string, cc *wantypes.CredentialCreation, prompt RegisterPrompt,\n) (*proto.MFARegisterResponse, error) {\n\tswitch {\n\tcase origin == \"\":\n\t\treturn nil, trace.BadParameter(\"origin required\")\n\tcase prompt == nil:\n\t\treturn nil, trace.BadParameter(\"prompt required\")\n\t}\n\tif err := cc.Validate(); err != nil {\n\t\treturn nil, trace.Wrap(err)\n\t}","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/gravitational/teleport/blob/1283425b60ec5f60d509ba4c791183d452923ff7/lib/auth/webauthncli/fido2.go#L425-L461","documentation":"After the FIDO2 credential prompt returns a choice, pickAssertion maps it back to the corresponding libfido2 assertion via a credToAssertion map. If the *CredentialInfo returned by prompt.PromptCredential is not a key in that map, the flow aborts with this error. It is a defensive check: a prompt that returns anything other than one of the options it was shown is considered invalid.","triggerScenarios":"During Login's device-selection/picker path, prompt.PromptCredential returns a *CredentialInfo that was not among the creds slice shown — e.g. a custom Prompt implementation returning a synthesized object, a stale pointer, or a copy.","commonSituations":"Custom CLI/Web UI prompt implementations (CLICredentialPrompt or bespoke ones) that re-create CredentialInfo instead of returning the passed pointer; test stubs; UI layers that serialize/deserialize the credential and break pointer identity.","solutions":["Ensure the custom PromptCredential implementation returns one of the exact *CredentialInfo pointers from its input slice.","If your prompt round-trips through JSON/UI, map the user's choice back to the original pointer from creds.","In test stubs, return creds[0] (an element of the input) instead of a newly constructed value.","Alternatively change lookup to match by credential ID rather than pointer identity (code change in fido2.go)."],"exampleFix":"// before (custom prompt returns a copy)\nfunc (p myPrompt) PromptCredential(creds []*CredentialInfo) (*CredentialInfo, error) {\n    return &CredentialInfo{ID: creds[p.idx].ID}, nil\n}\n// after\nfunc (p myPrompt) PromptCredential(creds []*CredentialInfo) (*CredentialInfo, error) {\n    return creds[p.idx], nil\n}","handlingStrategy":"type-guard","validationCode":"chosen, err := prompt.PromptCredential(creds)\nif err != nil { return nil, trace.Wrap(err) }\nif chosen == nil || !slices.Contains(creds, chosen) {\n    return nil, errors.New(\"prompt returned a credential outside the offered set\")\n}","typeGuard":"func isOfferedCredential(creds []*CredentialInfo, chosen *CredentialInfo) bool {\n    return chosen != nil && slices.Contains(creds, chosen)\n}","tryCatchPattern":"assertion, err := pickAssertion(ctx, cfg, assertions, user, prompt)\nif err != nil {\n    if strings.Contains(err.Error(), \"prompt returned invalid credential\") {\n        return nil, trace.BadParameter(\"PromptCredential must return one of the pointers it was given\")\n    }\n    return nil, trace.Wrap(err)\n}","preventionTips":["Prompt implementations must echo back an element of their input slice, never a copy.","Avoid serializing CredentialInfo through the UI layer (breaks pointer identity).","Add tests for every custom PromptCredential implementation asserting pointer membership."],"tags":["webauthn","fido2","pointer-identity"],"backgroundTag":"invalid-picker-selection","analyzedSha":"1283425b60ec5f60d509ba4c791183d452923ff7","analyzedAt":"2026-09-02T04:06:41.601Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}