Treat cached bytecode as executable output, not as disposable build residue. The safest pattern is to strip __pycache__ from release artefacts, rebuild from source in controlled environments, and require runtime validation for any hash-based cache that remains. That closes the gap between what reviewers inspect and what the interpreter actually runs.
Why poisoned .pyc caches are a release integrity problem, not just a build cleanup issue
Compiled Python bytecode is not harmless residue. In a release pipeline, a poisoned .pyc can let the artefact that reaches production diverge from the source reviewers approved, especially when build steps reuse cached interpreter output across environments. Security teams should treat bytecode as part of the trusted release surface and control it accordingly.
The practical risk is simple: if the pipeline consumes stale or attacker-influenced cache files, the interpreter may execute code that was never re-reviewed in the source tree. That makes cache hygiene, rebuild determinism, and artefact validation part of release security, not developer convenience.
For teams that already map supply-chain controls to release integrity, the same logic that supports SLSA also applies here, because the release must be reproducible from trusted inputs rather than from mutable by-products.
What a secure handling pattern looks like in the pipeline
The safest release pattern is to remove __pycache__ from release artefacts entirely, rebuild from source in a controlled environment, and generate fresh bytecode during the trusted build rather than carrying it forward from earlier stages. That keeps the bytecode lifecycle aligned with the source revision and the build environment that produced it.
If your process must retain caches for performance reasons, keep them strictly out of the artefact that is promoted, signed, or deployed. A cache may be acceptable as a local build optimisation, but it should not become a deployment input unless its provenance, interpreter version, and hash integrity are all validated at release time.
That distinction is also why release teams should avoid treating cached bytecode as if it were a non-executable temporary file. The release boundary is the point at which convenience must give way to integrity, because any artefact that the runtime can load is part of the security decision.
Where teams get tripped up and what controls close the gap
Poisoned caches become dangerous when build and deploy stages share workspace state, when caches persist across branches or runners, or when package installation and test execution happen before artefact sanitisation. In those cases, the pipeline can silently pick up cached bytecode that does not match the reviewed source set.
Security teams should make cache trust explicit: isolate build workspaces, invalidate interpreter caches on release boundaries, and verify that the final artefact is assembled only from source and known-good build outputs. If hash-based .pyc files are intentionally allowed, the release process should validate their hashes against the expected build context before promotion.
That aligns well with a broader pipeline-identity view: the build system should not inherit more trust than the artefact deserves, and the artefact should not inherit more authority than the source and build attestations can justify. NHIMG’s CI/CD Pipeline Identity Security Guide covers why untrusted build inputs, publishing tokens, and trust boundaries in delivery systems need explicit control.
What security teams should do before they sign off a release
What to verify: confirm that the release artefact is either free of cached bytecode or contains only bytecode generated inside the trusted build stage from the exact source revision being released. Verify that no shared workspace, runner cache, or reused container layer can reintroduce stale .pyc files.
Decision rule: if a cache file can affect runtime behaviour, treat it as executable output and either rebuild it deterministically or exclude it from the release package. If you cannot explain its provenance in the same way you explain the source artefact, do not promote it.
Common mistake: teams often scan source trees for malicious code but fail to inspect cached interpreter output with the same scepticism. That leaves a gap between code review and actual execution, which is exactly where poisoned bytecode becomes useful to an attacker.
Practitioner takeaway: release security for Python is not just about the source repository, it is about controlling every executable artefact the runtime may consume, including caches that were never meant to be trusted by default.
Standards & Framework Alignment
This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.
SLSA, NIST SP 800-53 Rev 5 and CIS Controls v8 set the governance and control requirements practitioners need to meet.
| Framework | Control / Reference | Relevance |
|---|---|---|
| SLSA | Supply-chain Levels for Software Artifacts | Release integrity depends on trusted build inputs and reproducible outputs. |
| Recommendation — Harden the build chain so release artefacts are reproducible from trusted source inputs. | ||
| NIST SP 800-53 Rev 5 | SI-7 — Software, Firmware, and Information Integrity | Poisoned bytecode is an integrity failure in what the runtime executes. |
| Recommendation — Validate release artefacts and reject cached outputs that cannot be trusted. | ||
| CIS Controls v8 | CIS-16 — Application Software Security | The issue sits in secure release handling for application code and artefacts. |
| Recommendation — Build release checks that prevent untrusted build artefacts from reaching production. | ||
Related resources from NHI Mgmt Group
- How should security teams handle exposed secrets in modern software pipelines?
- How should security teams handle Dependabot-style automation in CI pipelines?
- How should security teams handle cloud secrets that are shared across applications and pipelines?
- How should security teams handle protobuf vulnerabilities in CI/CD pipelines?