{"record":{"id":"296620dcd10dee43","repo":"gravitational/teleport","slug":"picker-returned-invalid-credential-v","errorCode":null,"errorMessage":"picker returned invalid credential: %#v","messagePattern":"picker returned invalid credential: %#v","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/auth/touchid/api.go","lineNumber":598,"sourceCode":"\tpromptOnce()\n\tvar choice *CredentialInfo\n\tvar choiceErr error\n\tif err := actx.Guard(func() {\n\t\tchoice, choiceErr = picker.PromptCredential(deduped)\n\t}); err != nil {\n\t\treturn nil, trace.Wrap(err)\n\t}\n\tif choiceErr != nil {\n\t\treturn nil, trace.Wrap(choiceErr)\n\t}\n\n\t// Is choice a pointer within the slice?\n\t// We could work around this requirement, but it seems better to constrain the\n\t// picker API from the start.\n\tif slices.Contains(deduped, choice) {\n\t\treturn choice, nil\n\t}\n\treturn nil, fmt.Errorf(\"picker returned invalid credential: %#v\", choice)\n}\n\n// ListCredentials lists all registered Secure Enclave credentials.\n// Requires user interaction.\nfunc ListCredentials() ([]CredentialInfo, error) {\n\tif !IsAvailable() {\n\t\treturn nil, ErrNotAvailable\n\t}\n\n\tpromptPlatform()\n\tinfos, err := native.ListCredentials()\n\tif err != nil {\n\t\treturn nil, trace.Wrap(err)\n\t}\n\n\t// Parse public keys.\n\tfor i := range infos {\n\t\tinfo := &infos[i]","sourceCodeStart":580,"sourceCodeEnd":616,"githubUrl":"https://github.com/gravitational/teleport/blob/1283425b60ec5f60d509ba4c791183d452923ff7/lib/auth/touchid/api.go#L580-L616","documentation":"pickCredential validates that the credential chosen by the native macOS picker (user interaction dialog) actually points at one of the credentials that were offered for selection. Because the picker returns a pointer, it must be an element of the deduplicated candidate slice; anything else would break the credential-to-assertion mapping downstream. This error means the picker implementation returned a pointer outside that slice.","triggerScenarios":"During Login, the native picker dialog returns a *CredentialInfo pointer that is not contained in the deduped candidate slice — typically due to a bug or mismatch between the credentials passed to the picker and the value it echoes back.","commonSituations":"Custom or modified picker implementations (platform-specific code) returning copied/re-allocated CredentialInfo values instead of the original pointers; concurrency bugs where the candidate slice was rebuilt after the picker captured a pointer; test doubles for the picker returning fresh objects.","solutions":["Fix the picker implementation to return one of the exact *CredentialInfo pointers it was given, not a copy.","Ensure the candidate credential slice is not reallocated or rebuilt between showing the picker and reading its result.","If using a test/stub picker, make it return an element of the slice passed to it (e.g. creds[0]).","Replace pointer identity matching with credential ID matching if copies are unavoidable (requires code change in pickCredential)."],"exampleFix":"// before (stub picker returns a copy)\nfunc (p fakePicker) PromptCredential(creds []*CredentialInfo) (*CredentialInfo, error) {\n    return &CredentialInfo{ID: creds[0].ID}, nil\n}\n// after\nfunc (p fakePicker) PromptCredential(creds []*CredentialInfo) (*CredentialInfo, error) {\n    return creds[0], nil\n}","handlingStrategy":"validation","validationCode":"choice, err := picker.PromptCredential(deduped)\nif err == nil && choice != nil && !slices.Contains(deduped, choice) {\n    return errors.New(\"picker returned a credential outside the offered set\")\n}","typeGuard":"func isValidChoice(choices []*CredentialInfo, choice *CredentialInfo) bool {\n    return choice != nil && slices.Contains(choices, choice)\n}","tryCatchPattern":"cred, err := pickCredential(deduped, picker)\nif err != nil {\n    if strings.Contains(err.Error(), \"picker returned invalid credential\") {\n        return nil, trace.BadParameter(\"picker implementation bug: it must return one of the offered pointers\")\n    }\n    return nil, trace.Wrap(err)\n}","preventionTips":["Picker implementations must return an element of the input slice verbatim — never a copy.","Do not rebuild/reallocate the credential slice after handing it to the picker.","Add a contract test asserting the stub/real picker returns an in-slice pointer."],"tags":["touchid","macos","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"}