Whitelist uses of stdlib random module for bandit
[mudpy.git] / mudpy / misc.py
index dc37b6e..5beb3b6 100644 (file)
@@ -1218,7 +1218,9 @@ def weighted_choice(data):
             expanded.append(key)
 
     # return one at random
-    return random.choice(expanded)
+    # Whitelist the random.randrange() call in bandit since it's not used for
+    # security/cryptographic purposes
+    return random.choice(expanded)  # nosec
 
 
 def random_name():
@@ -1265,7 +1267,9 @@ def random_name():
     name = ""
 
     # create a name of random length from the syllables
-    for _syllable in range(random.randrange(2, 6)):
+    # Whitelist the random.randrange() call in bandit since it's not used for
+    # security/cryptographic purposes
+    for _syllable in range(random.randrange(2, 6)):  # nosec
         name += weighted_choice(syllables)
 
     # strip any leading quotemark, capitalize and return the name
@@ -1585,16 +1589,15 @@ def get_menu_choices(user):
     state = universe.groups["menu"][user.state]
     create_choices = state.get("create")
     if create_choices:
-        choices = eval(create_choices)
+        choices = call_hook_function(create_choices, (user,))
     else:
         choices = {}
     ignores = []
     options = {}
     creates = {}
     for facet in state.facets():
-        if facet.startswith("demand_") and not eval(
-           universe.groups["menu"][user.state].get(facet)
-           ):
+        if facet.startswith("demand_") and not call_hook_function(
+                universe.groups["menu"][user.state].get(facet), (user,)):
             ignores.append(facet.split("_", 2)[1])
         elif facet.startswith("create_"):
             creates[facet] = facet.split("_", 2)[1]
@@ -1602,7 +1605,8 @@ def get_menu_choices(user):
             options[facet] = facet.split("_", 2)[1]
     for facet in creates.keys():
         if not creates[facet] in ignores:
-            choices[creates[facet]] = eval(state.get(facet))
+            choices[creates[facet]] = call_hook_function(
+                state.get(facet), (user,))
     for facet in options.keys():
         if not options[facet] in ignores:
             choices[options[facet]] = state.get(facet)
@@ -1677,6 +1681,28 @@ def get_choice_action(user):
         return ""
 
 
+def call_hook_function(fname, arglist):
+    """Safely execute named function with supplied arguments, return result."""
+
+    # all functions relative to mudpy package
+    function = mudpy
+
+    for component in fname.split("."):
+        try:
+            function = getattr(function, component)
+        except AttributeError:
+            log('Could not find mudpy.%s() for arguments "%s"'
+                % (fname, arglist), 7)
+            function = None
+            break
+    if function:
+        try:
+            return function(*arglist)
+        except Exception:
+            log('Calling mudpy.%s(%s) raised an exception...\n%s'
+                % (fname, (*arglist,), traceback.format_exc()), 7)
+
+
 def handle_user_input(user):
     """The main handler, branches to a state-specific handler."""
 
@@ -1711,7 +1737,9 @@ def generic_menu_handler(user):
     if not user.choice:
         user.choice = get_default_menu_choice(user.state)
     if user.choice in user.menu_choices:
-        exec(get_choice_action(user))
+        action = get_choice_action(user)
+        if action:
+            call_hook_function(action, (user,))
         new_state = get_choice_branch(user)
         if new_state:
             user.state = new_state
@@ -1890,6 +1918,7 @@ def handler_active(user):
         ran = False
         if actor.can_run(command):
             # dereference the relative object path for the requested function
+            # TODO(fungi) use call_hook_function() here instead
             action = mudpy
             action_fname = command.get("action", command.key)
             for component in action_fname.split("."):