grpc/grpc-go · error

local credentials rejected connection to non-local address

Error message

local credentials rejected connection to non-local address %q

What it means

Returned by getSecurityLevel (during ClientHandshake/ServerHandshake) when the peer address is not recognized as a local connection: it is not 127.x, not [::1], not a Windows named pipe, and not a unix socket. The local credentials package is intentionally unsafe for remote peers, so it refuses the handshake.

Solutions

  1. Only use local.NewCredentials() for loopback (127.0.0.1 / ::1) or unix-socket connections.
  2. For non-local peers, switch to credentials.NewTLS or alts.NewClientCreds.
  3. Connect via unix://<socket-path> or 127.0.0.1:<port> when using local credentials.

Example fix

// before
conn, _ := grpc.Dial("10.0.0.5:50051", grpc.WithTransportCredentials(local.NewCredentials()))
// after
conn, _ := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(local.NewCredentials()))
// or
conn, _ := grpc.Dial("unix:///tmp/my.sock", grpc.WithTransportCredentials(local.NewCredentials()))
Defensive patterns

Strategy: validation

Validate before calling

func isLocalAddr(addr string) bool {
    return strings.HasPrefix(addr, "127.") ||
        strings.HasPrefix(addr, "[::1]") ||
        strings.HasPrefix(addr, "unix:")
}

if !isLocalAddr(peerAddr) {
    log.Fatalf("local credentials only valid for loopback/UDS, got %q", peerAddr)
}

Type guard

func safeForLocalCreds(network, addr string) bool {
    switch {
    case strings.HasPrefix(addr, "127."), strings.HasPrefix(addr, "[::1]:"):
        return true
    case network == "pipe" && strings.HasPrefix(addr, `\\.\pipe\`):
        return true
    case network == "unix":
        return true
    }
    return false
}

Prevention

When it happens

Trigger: Using local.NewCredentials() on a connection to a non-loopback IP (e.g. 10.0.0.5, a public address) or a TCP connection to the machine's external IP; connecting via a hostname that resolves to a non-loopback address.

Common situations: Local-dev convenience credential accidentally used in a multi-node cluster; service binding to 0.0.0.0 and connecting via the pod IP; switching from UDS to TCP without changing the credential.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/fb1d62310aad0b00. Report an issue: GitHub.

Appendix: source

Thrown at credentials/local/local.go:82

	return c.info
}

// getSecurityLevel returns the security level for a local connection.
// It returns an error if a connection is not local.
func getSecurityLevel(network, addr string) (credentials.SecurityLevel, error) {
	switch {
	// Local TCP connection
	case strings.HasPrefix(addr, "127."), strings.HasPrefix(addr, "[::1]:"):
		return credentials.NoSecurity, nil
	// Windows named pipe connection
	case network == "pipe" && strings.HasPrefix(addr, `\\.\pipe\`):
		return credentials.NoSecurity, nil
	// UDS connection
	case network == "unix":
		return credentials.PrivacyAndIntegrity, nil
	// Not a local connection and should fail
	default:
		return credentials.InvalidSecurityLevel, fmt.Errorf("local credentials rejected connection to non-local address %q", addr)
	}
}

func (*localTC) ClientHandshake(_ context.Context, _ string, conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
	secLevel, err := getSecurityLevel(conn.RemoteAddr().Network(), conn.RemoteAddr().String())
	if err != nil {
		return nil, nil, err
	}
	return conn, info{credentials.CommonAuthInfo{SecurityLevel: secLevel}}, nil
}

func (*localTC) ServerHandshake(conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
	secLevel, err := getSecurityLevel(conn.RemoteAddr().Network(), conn.RemoteAddr().String())
	if err != nil {
		return nil, nil, err
	}
	return conn, info{credentials.CommonAuthInfo{SecurityLevel: secLevel}}, nil
}

View on GitHub (pinned to 0c51461d27)