shadow1ng/fscan · error

auth function panic: %v

Error message

auth function panic: %v

What it means

This error is produced by a panic-recovery wrapper around the per-credential auth function. TestSingleCredential runs authFn inside a goroutine with a deferred recover(); if the user-supplied auth function panics (nil map write, nil pointer dereference, index out of range), the panic is converted into a failed AuthResult with ErrorTypeUnknown carrying this message. It indicates a bug inside the auth callback, not a network or credential problem.

Source

Thrown at plugins/services/credential_tester.go:105

		}
	}
	if err := ctx.Err(); err != nil {
		return &AuthResult{
			Success:   false,
			ErrorType: ErrorTypeNetwork,
			Error:     err,
		}
	}

	resultChan := make(chan *AuthResult, 1)

	go func() {
		defer func() {
			if r := recover(); r != nil {
				resultChan <- &AuthResult{
					Success:   false,
					ErrorType: ErrorTypeUnknown,
					Error:     fmt.Errorf("auth function panic: %v", r),
				}
			}
		}()
		result := authFn(ctx, cred)
		resultChan <- result
	}()

	select {
	case result := <-resultChan:
		return result
	case <-ctx.Done():
		// context 被取消后只做有界等待,避免 authFn 卡死时清理 goroutine 也永久泄漏。
		go func() {
			timer := time.NewTimer(authCleanupWait())
			defer timer.Stop()

			select {
			case result := <-resultChan:

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Inspect the %v payload in the message to find the panic value (usually 'runtime error: nil pointer dereference' or 'index out of range') and fix the offending line in the authFn callback.
  2. Add nil/empty checks for cred.Username, cred.Password and any parsed response fields inside the auth function before using them.
  3. Wrap risky parsing logic inside the authFn in its own recover() if partial results are acceptable.
  4. Test the authFn against empty/zero-value Credential structs in unit tests to surface panics early.

Example fix

// before
result := conn.Auth(cred.Username, cred.Password, parseDomains(resp))
// after
if cred.Username == "" || cred.Password == "" {
    return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: errors.New("empty credential")}
}
result := conn.Auth(cred.Username, cred.Password, parseDomains(resp))
Defensive patterns

Strategy: try-catch

Validate before calling

if cred == (Credential{}) || authFn == nil {
    return errors.New("invalid credential or auth function")
}

Type guard

func safeAuthFn(authFn AuthFn) AuthFn {
    return func(ctx context.Context, cred Credential) *AuthResult {
        defer func() { _ = recover() }()
        return authFn(ctx, cred)
    }
}

Try / catch

res := <-resultChan
if res.Error != nil && strings.HasPrefix(res.Error.Error(), "auth function panic:") {
    // recoverable: log panic payload, mark credential attempt as errored, continue
    log.Printf("authFn panicked: %v", res.Error)
    return
}

Prevention

When it happens

Trigger: Calling Scan/TestCredentialsConcurrently with an authFn that panics on the given ctx/credential — e.g. dereferencing a nil field in the Credential, indexing a slice without bounds checks, or writing to a nil map when parsing the service response.

Common situations: Custom auth functions written without nil checks; credentials with empty Username/Password fields the callback assumes are populated; a service banner response that the callback parses unconditionally; refactoring the callback so a field becomes nil at runtime.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06). Data as JSON: /api/errors/8953030c04861079. Report an issue: GitHub.