cfgaudit

Rules / CFG129

CFG129 committed Python bytecode runs instead of its source

Severity: error (an unchecked-hash .pyc in __pycache__ with its source beside it) · warn (an unchecked-hash .pyc with no source yet, bytecode outside __pycache__ with no source) OWASP: LLM04:2026 – Supply Chain Agents: claude-code, cline, codex, continue, copilot, cursor, devin, devin-desktop, gemini-cli, goose, grok, junie, kimi-code, opencode, qwen-code, roo-code, vscode, zed

What cfgaudit checks

A hook, task, MCP launch or skill runs a Python script, and that script imports a module beside it. Whatever reads only the .py files reviews the source, but Python may load a committed .pyc instead (#1398, AVE-2026-00075). Since PEP 552 a .pyc header can mark the file as hash-based and unchecked, which tells Python not to look at the source at all.

Measured with CPython 3.12.3 on python main.py, where main.py runs import helper:

.pyc helper.py beside it Unchecked hash Checked hash Timestamp
__pycache__/helper.cpython-312.pyc yes bytecode runs source runs source runs
__pycache__/helper.cpython-312.pyc no not imported not imported not imported
helper.pyc outside __pycache__ yes source runs source runs source runs
helper.pyc outside __pycache__ no bytecode runs bytecode runs bytecode runs

So CFG129 reports:

Not reported: timestamp and checked-hash files in __pycache__, which Python validates against the source. A timestamp file is what an accidentally committed __pycache__ holds, and after a clone its recorded time no longer matches. Also not reported: a .pyc older than Python 3.7, and a file that is not bytecode.

Only the first eight bytes of the file are read, the magic number and the flags. cfgaudit does not analyse the bytecode, so it cannot tell whether the code differs from the source, and it runs nothing. In a git checkout only tracked files count, so a local __pycache__ is not reported. Without a git index, as in an unpacked download, every .pyc outside node_modules counts.

The cache tag must match the interpreter (cpython-312 for Python 3.12), so an attacker ships one file per version. Each is reported.

Trigger example

A skills/review/scripts/ directory with main.py, a harmless helper.py, and __pycache__/helper.cpython-312.pyc compiled from other code with py_compile's UNCHECKED_HASH mode.

Safe alternative

Do not commit bytecode. Add __pycache__/ and *.pyc to .gitignore, and commit the source of any module the project needs.

References

Documentation for cfgaudit 1.21.0. Source of this page · Rule implementation