gravitational/teleport · error

picker returned invalid credential: %#v

Error message

picker returned invalid credential: %#v

What it means

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.

Source

Thrown at lib/auth/touchid/api.go:598

	promptOnce()
	var choice *CredentialInfo
	var choiceErr error
	if err := actx.Guard(func() {
		choice, choiceErr = picker.PromptCredential(deduped)
	}); err != nil {
		return nil, trace.Wrap(err)
	}
	if choiceErr != nil {
		return nil, trace.Wrap(choiceErr)
	}

	// Is choice a pointer within the slice?
	// We could work around this requirement, but it seems better to constrain the
	// picker API from the start.
	if slices.Contains(deduped, choice) {
		return choice, nil
	}
	return nil, fmt.Errorf("picker returned invalid credential: %#v", choice)
}

// ListCredentials lists all registered Secure Enclave credentials.
// Requires user interaction.
func ListCredentials() ([]CredentialInfo, error) {
	if !IsAvailable() {
		return nil, ErrNotAvailable
	}

	promptPlatform()
	infos, err := native.ListCredentials()
	if err != nil {
		return nil, trace.Wrap(err)
	}

	// Parse public keys.
	for i := range infos {
		info := &infos[i]

View on GitHub (pinned to 1283425b60)

Solutions

  1. Fix the picker implementation to return one of the exact *CredentialInfo pointers it was given, not a copy.
  2. Ensure the candidate credential slice is not reallocated or rebuilt between showing the picker and reading its result.
  3. If using a test/stub picker, make it return an element of the slice passed to it (e.g. creds[0]).
  4. Replace pointer identity matching with credential ID matching if copies are unavoidable (requires code change in pickCredential).

Example fix

// before (stub picker returns a copy)
func (p fakePicker) PromptCredential(creds []*CredentialInfo) (*CredentialInfo, error) {
    return &CredentialInfo{ID: creds[0].ID}, nil
}
// after
func (p fakePicker) PromptCredential(creds []*CredentialInfo) (*CredentialInfo, error) {
    return creds[0], nil
}
Defensive patterns

Strategy: validation

Validate before calling

choice, err := picker.PromptCredential(deduped)
if err == nil && choice != nil && !slices.Contains(deduped, choice) {
    return errors.New("picker returned a credential outside the offered set")
}

Type guard

func isValidChoice(choices []*CredentialInfo, choice *CredentialInfo) bool {
    return choice != nil && slices.Contains(choices, choice)
}

Try / catch

cred, err := pickCredential(deduped, picker)
if err != nil {
    if strings.Contains(err.Error(), "picker returned invalid credential") {
        return nil, trace.BadParameter("picker implementation bug: it must return one of the offered pointers")
    }
    return nil, trace.Wrap(err)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gravitational/teleport@1283425b60 (2026-09-02). Data as JSON: /api/errors/296620dcd10dee43. Report an issue: GitHub.