Roadmap / Code Generation & Verification

✓

Static Safety Checks with ast

mediumcode-generationsecuritystatic-analysis

The problem

Some dangers in generated code are visible without running it: an import of a module that can touch the system, a call to eval, a trick to escape a restricted namespace. Python's ast module parses source code into an abstract syntax tree (the structure of the program, as a tree of nodes) that a program can walk. You will write a scanner that lists the risks of a piece of code, line by line.

What to flag

  • Imports of a dangerous module: import X, import X.y or from X import ... where the top-level package X is one of os, subprocess, socket, shutil, sys, ctypes, pickle. Message: "import <X>".
  • Calls of a dangerous builtin by name: eval, exec, compile, open, __import__, getattr, globals. Message: "call <name>". Only direct calls by name count (eval(...)), not methods (obj.eval(...)).
  • Dunder attribute access: any attribute whose name starts and ends with __ (a "dunder", for double underscore), such as ().__class__ or x.__subclasses__. Message: "dunder <name>".

If the source does not parse, return [(0, "syntax error")].

Example. This source, one statement per line:

import os
x = eval(input())
y = ().__class__
from subprocess import run
obj.eval(1)

The findings are [(1, "import os"), (2, "call eval"), (3, "dunder __class__"), (4, "import subprocess")]. Line 5 is not flagged: eval is called as a method of obj, not by name. The scan never runs the code.

def f(: does not parse: [(0, "syntax error")].

What to implement

find_risks(source): a list of (line, message) findings, sorted by line. Within a line, imports come first, then calls, then dunders, each in source order.

Requirements

  • Use ast.parse and ast.walk; do not run the code.
  • Sort findings by line with a stable sort, so that the order within a line is kept.

Hint

The node types are ast.Import (.names[i].name), ast.ImportFrom (.module), ast.Call with .func an ast.Name (.id), and ast.Attribute (.attr). Every node has .lineno. ast.walk(tree) visits every node.

solution.py
Python idle

Press Run (⌘/Ctrl + Enter) to test your code.