Public preview · Registration and live billing are not open.
Anti-AIClonePILOT

Log date · Fri

CPyGraph (arXiv 2609.25083): version-aware static analysis of native CPython bytecode

An arXiv paper submitted on 2026-09-18 presented package-level static analysis of CPython 3.10–3.14 bytecode, a reminder that shipping only bytecode does not hide logic.

What happened on 2026-09-18

At 21:25 UTC on 2026-09-18, Baihong Chen and Wen Li submitted v1 of 'CPyGraph' (arXiv 2609.25083), filed under cs.CR. Native CPython bytecode is the form in which Python programs actually run, the paper notes, but its instructions, call handling, stack layout and exception representation change between releases, so analysis tools have to be version-aware. CPyGraph is a C++ framework. Version-specific adapters expose each release's semantics through one shared interface while preserving code-object identities and native bytecode offsets. An Andersen-style points-to analysis that models the operand stack is computed together with the call graph until both reach a fixed point. On that shared state the framework builds exception-aware control-flow graphs, control-dependence graphs and interprocedural data-dependence graphs. On the PYGBench suite of 201 package-level programs and 1,733 candidates, the default configuration on CPython 3.10 reached 91.40% candidate precision with 100% recall. Across CPython 3.10 to 3.14, 1,328 of 1,334 version-invariant queries gave consistent results.

What it means for protecting Python apps

Many desktop tools, CLI products and internal clients are written in Python and often ship as bytecode without the .py sources, and developers sometimes count that as a layer of protection. CPyGraph's goal is security-oriented static analysis of Python packages, not source recovery, so it would be wrong to say it decompiles anything. It does establish something defenders should note. Bytecode alone is enough for structured analysis such as call graphs, data flow and control dependence. The frequent changes between CPython versions are also being absorbed into tooling rather than standing in the way. So the assumption that shipping only bytecode, perhaps on a newer Python version, keeps others from understanding your logic should be read as a weak cost barrier, not as confidentiality. One honest limit: the evaluation focuses on benchmark programs, and the abstract does not cover bundles processed by commercial hardening or custom loaders.

Checks to run on your own builds

First, unpack your own release bundle and list the Python artifacts it really contains: bytecode, any leftover .py files, configuration and data files. Confirm that no test code or internal scripts slipped in. Second, move decisions that genuinely need to stay confidential, such as license validation, pricing and abuse rules, to the server, and keep the client to making calls and displaying results. Third, scan the packaged output for keys, tokens and internal URLs, which remain readable as constants inside bytecode. Fourth, if you rely on a hardening or encrypted-loader scheme, re-verify both functionality and protection every time you upgrade Python or the packaging tool, and keep a record of what you found.

Your checklist

  • Unpack release bundles and inventory bytecode, stray sources and config files
  • Move license, pricing and abuse decisions to the server
  • Scan bytecode constants for keys, tokens and internal URLs

Primary sources

All sources and reproduction status ↗