Beyond Simple Name Checks
The eval() and print() examples work because those functions are called by their bare name. Real-world linting rules are often more complex. You may need to detect:
- Attribute calls:
os.system("rm -rf /")— the function is accessed as an attribute of a module. - Forbidden imports:
import pickle— the violation is the import itself, not a call. - Structural patterns: Any function that is
async defbut does not have a timeout parameter.
Detecting attribute calls requires checking ast.Attribute nodes instead of ast.Name:
def visit_Call(self, node):
# Detect os.system() calls
if (
isinstance(node.func, ast.Attribute)
and node.func.attr == 'system'
and isinstance(node.func.value, ast.Name)
and node.func.value.id == 'os'
):
print(f"Line {node.lineno}: Forbidden call to os.system()")
self.generic_visit(node)
Detecting forbidden imports uses visit_Import and visit_ImportFrom:
FORBIDDEN_MODULES = {'pickle', 'shelve', 'marshal'}
def visit_Import(self, node):
for alias in node.names:
if alias.name in FORBIDDEN_MODULES:
print(f"Line {node.lineno}: Forbidden import '{alias.name}'")
def visit_ImportFrom(self, node):
if node.module in FORBIDDEN_MODULES:
print(f"Line {node.lineno}: Forbidden import from '{node.module}'")
A note on robustness: The attribute call check above (node.func.value.id == 'os') only catches the case where os is used directly. It would miss import os as operating_system; operating_system.system(...). Full alias tracking requires scope analysis, which is where Pylint's Astroid library (with its type inference) becomes significantly more powerful than the bare ast module. For simple rules, ast is sufficient. For rules that need to follow variable assignments and aliases, use Astroid.
If an interviewer asks "what are the limitations of AST-based linting?", the strong answer covers two areas: (1) alias tracking — an AST linter checking for os.system won't catch import os as o; o.system(...) without scope analysis, and (2) dynamic code — getattr(module, func_name)() is a call that cannot be statically analyzed because the function name is a runtime value. These limitations are why AST linting is complementary to, not a replacement for, runtime security controls.