Skip to content

Instantly share code, notes, and snippets.

@coder-chenzhi
Created March 11, 2021 14:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save coder-chenzhi/3e348c0c9de4cd6f968a6390dab1011c to your computer and use it in GitHub Desktop.
Save coder-chenzhi/3e348c0c9de4cd6f968a6390dab1011c to your computer and use it in GitHub Desktop.
import os
import ast
PROJECTS_HOME = r"E:\OSS\ansible_collections"
SENSITIVE_KEYS = "passw"
def get_key_from_dict(tree, key):
"""
:param tree: ast tree to traverse, usually is the value of a dict
:param key: key to search, usually is 'type' or 'no_log'
:return: if key exist in tree, return the node, if not exist, return None
"""
for value in ast.walk(tree):
if isinstance(value, ast.Dict):
for i, key_name in enumerate(value.keys):
if isinstance(key_name, ast.Str):
if key in key_name.s:
return value.values[i]
if isinstance(key_name, ast.Name):
if key in key_name.id:
return value.values[i]
if isinstance(value, ast.Call) and isinstance(value.func, ast.Name) and value.func.id == "dict":
for keyword in value.keywords:
if key in keyword.arg:
return keyword.value
return None
def traverse_dict(tree):
def _check(name, value):
type = get_key_from_dict(value, "type")
default = get_key_from_dict(value, "default")
choices = get_key_from_dict(value, "choices")
if type is not None and "str" in ast.dump(type) and default is None and choices is None:
print("Found sensitive key:", name)
no_log = get_key_from_dict(value, "no_log")
if no_log is None:
print("Missing of no_log for sensitive key at line {line}: {name}".format(
line=value.lineno, name=name))
for value in ast.walk(tree):
real_name = None
if isinstance(value, ast.Dict):
for i, key_name in enumerate(value.keys):
if isinstance(key_name, ast.Str):
if SENSITIVE_KEYS in key_name.s:
real_name = key_name.s
real_value = value.values[i]
if isinstance(key_name, ast.Name):
if SENSITIVE_KEYS in key_name.id:
real_name = key_name.id
real_value = value.values[i]
if real_name is not None:
_check(real_name, real_value)
if isinstance(value, ast.Call) and isinstance(value.func, ast.Name) and value.func.id == "dict":
for keyword in value.keywords:
if SENSITIVE_KEYS in keyword.arg:
real_name = keyword.arg
real_value = keyword.value
_check(real_name, real_value)
if __name__ == '__main__':
projects = os.listdir(PROJECTS_HOME)
for project in projects:
for root, dirs, files in os.walk(os.path.join(PROJECTS_HOME, project), topdown=False):
for name in files:
if not name.endswith(".py"):
continue
file_path = os.path.join(root, name)
with open(file_path, "r", encoding="UTF-8", errors="ignore") as file_handler:
code = file_handler.read()
print(file_path)
tree = ast.parse(code)
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
for name in node.targets:
if isinstance(name, ast.Name) and "argument_spec" in name.id:
traverse_dict(node.value)
if isinstance(node, ast.Call):
func = node.func
if isinstance(func, ast.Attribute):
if "argument_spec" in ast.dump(func.value) and "update" == func.attr:
for arg in node.args:
traverse_dict(arg)
for keyword in node.keywords:
traverse_dict(keyword)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment