diff --git a/NEWS b/NEWS
index 6516412b8b7c2758de9b9f66df9a6ac2d00d802d..ec983769bee4482cc66cdf7c74a10ef5d3395cf1 100644
--- a/NEWS
+++ b/NEWS
@@ -101,6 +101,15 @@ Details
 - Improved burnin
 
 
+Version 2.0.5
+-------------
+
+- Fix security issue due to missing validation of iallocator names; this
+  allows local and remote execution of arbitrary executables
+- Fix failure of gnt-node list during instance removal
+- Ship the RAPI documentation in the archive
+
+
 Version 2.0.4
 -------------
 
diff --git a/lib/backend.py b/lib/backend.py
index f5f258bd25c1cb6439b4d343015420beed28b716..a93397e5c0e0c6e4b4d65a2e12f39bb4523858f2 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -1731,10 +1731,11 @@ def _TryOSFromDisk(name, base_dir=None):
   """
   if base_dir is None:
     os_dir = utils.FindFile(name, constants.OS_SEARCH_PATH, os.path.isdir)
-    if os_dir is None:
-      return False, "Directory for OS %s not found in search path" % name
   else:
-    os_dir = os.path.sep.join([base_dir, name])
+    os_dir = utils.FindFile(name, [base_dir], os.path.isdir)
+
+  if os_dir is None:
+    return False, "Directory for OS %s not found in search path" % name
 
   status, api_versions = _OSOndiskAPIVersion(name, os_dir)
   if not status:
@@ -2612,8 +2613,6 @@ class HooksRunner(object):
   on the master side.
 
   """
-  RE_MASK = re.compile("^[a-zA-Z0-9_-]+$")
-
   def __init__(self, hooks_base_dir=None):
     """Constructor for hooks runner.
 
@@ -2721,7 +2720,7 @@ class HooksRunner(object):
     for relname in dir_contents:
       fname = os.path.join(dir_name, relname)
       if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
-          self.RE_MASK.match(relname) is not None):
+              constants.EXT_PLUGIN_MASK.match(relname) is not None):
         rrval = constants.HKR_SKIP
         output = ""
       else:
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index c149a229d49f404db72a84c049eda77bffd32b7a..c837cd90cbf23ce93dffa99d7bb2e39d871858c7 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -2547,10 +2547,9 @@ class LUQueryNodes(NoHooksLU):
     inst_fields = frozenset(("pinst_cnt", "pinst_list",
                              "sinst_cnt", "sinst_list"))
     if inst_fields & frozenset(self.op.output_fields):
-      instancelist = self.cfg.GetInstanceList()
+      inst_data = self.cfg.GetAllInstancesInfo()
 
-      for instance_name in instancelist:
-        inst = self.cfg.GetInstanceInfo(instance_name)
+      for instance_name, inst in inst_data.items():
         if inst.primary_node in node_to_primary:
           node_to_primary[inst.primary_node].add(inst.name)
         for secnode in inst.secondary_nodes:
diff --git a/lib/constants.py b/lib/constants.py
index 0f035ef79eaf4d9564095637e44869ad0df340d0..7fb61a2ee8f96843202021b79d410e4e606d4cdc 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -21,6 +21,8 @@
 
 """Module holding different constants."""
 
+import re
+
 from ganeti import _autoconf
 
 # various versions
@@ -174,6 +176,9 @@ VALUE_NONE = "none"
 VALUE_TRUE = "true"
 VALUE_FALSE = "false"
 
+# External script validation mask
+EXT_PLUGIN_MASK = re.compile("^[a-zA-Z0-9_-]+$")
+
 # hooks-related constants
 HOOKS_BASE_DIR = CONF_DIR + "/hooks"
 HOOKS_PHASE_PRE = "pre"
diff --git a/lib/utils.py b/lib/utils.py
index 91baa8849da1d333e2333b98df3e48c868a80e73..8f7d5579ef3d5e30ec40a5d5e744244d38318b9e 100644
--- a/lib/utils.py
+++ b/lib/utils.py
@@ -1656,9 +1656,17 @@ def FindFile(name, search_path, test=os.path.exists):
   @return: full path to the object if found, None otherwise
 
   """
+  # validate the filename mask
+  if constants.EXT_PLUGIN_MASK.match(name) is None:
+    logging.critical("Invalid value passed for external script name: '%s'",
+                     name)
+    return None
+
   for dir_name in search_path:
     item_name = os.path.sep.join([dir_name, name])
-    if test(item_name):
+    # check the user test and that we're indeed resolving to the given
+    # basename
+    if test(item_name) and os.path.basename(item_name) == name:
       return item_name
   return None