Bandit walks the Python AST looking for known-dangerous constructs: subprocess with shell=True, yaml.load without a safe loader, assert used for validation, hardcoded passwords.
It is the first scan to run and the cheapest to keep. The series uses it as the honest baseline in every lab, including where it comes back clean on genuinely vulnerable code — Bandit knows Python, not Django, so an ORM .raw() call built with an f-string is invisible to it. That gap is exactly where the custom Semgrep rules start.