Does PyInstaller Protect Python Source Code? The Truth About Python EXE Files
No. PyInstaller bundles your compiled .pyc bytecode plus the Python interpreter into one executable so it *runs* without Python installed — but it does not obfuscate or encrypt anything. A free tool called pyinstxtractor unpacks the exe back into .pyc files, which decompile to near-original source and leak string literals. PyInstaller solves distribution, not protection. To actually protect source, obfuscate or compile it *before* packaging.
This is one of the most common — and most costly — misunderstandings in Python distribution. People ship a PyInstaller .exe believing the source is now safe inside an opaque binary. It isn't, and I want to show exactly why with a real build, not assertions.
Here's what an .exe actually contains, and how quickly your code comes back out.
What PyInstaller actually does
PyInstaller is a packager. It answers one question — "how do I run this on a machine without Python installed?" — and it answers it well. The pipeline:
your_script.py| (Python compiles it)v.pyc bytecode| (PyInstaller bundles)vEXE = [ bootloader + python3xx.dll + your .pyc files + libraries ]
Notice what's *not* in that pipeline: no obfuscation, no encryption. Your .pyc files are placed inside the executable as-is. The exe is bigger and self-contained — I built one earlier that came to 9.17 MB and ran standalone — but the bytecode inside is untouched.
Proof: extracting the code back out
A --onefile exe unpacks itself to a temp folder at runtime (the _MEIPASS path). A widely-available tool, pyinstxtractor, does the same thing offline — it reads the exe and writes out every bundled .pyc:
$ python pyinstxtractor.py myapp.exe[+] Processing myapp.exe[+] Pysca archive found[+] Extracting pyz...[+] Successfully extracted to myapp.exe_extracted/$ ls myapp.exe_extracted/myapp.pyc PYZ-00.pyz_extracted/ python311.dll ...
Now you're holding the original .pyc files — and as I showed when I decompiled a .pyc, those decompile to near-original source and leak every string literal in plaintext. In --onedir mode it's even more open: the _internal/ folder literally contains python311.dll and your .pyc files sitting in the clear. The .exe bought you distribution, not secrecy.
The markers are unmistakable too: strings like _MEIPASS and a bundled python3xx.dll instantly identify any file as a PyInstaller-packed Python app — telling an analyst exactly which tool to reach for.
Packaging ≠ obfuscation
This is the mental model to keep: packaging and protection are different problems.
| Packaging (PyInstaller) | Protection (obfuscate / compile) | |
|---|---|---|
| Goal | Run without Python installed | Make source hard to recover |
| Your bytecode | Bundled as-is | Rewritten or removed |
| Strings/secrets | In plaintext | Encrypted (obfuscation) |
| Reversible? | Yes — pyinstxtractor | Much harder |
PyInstaller is excellent at its job — it's just not the job you thought. (For how it compares to a true compiler, see PyInstaller vs Nuitka.)
How to actually protect a Python exe
The fix is order of operations: protect the source first, then package it. Now even after someone unpacks the exe and decompiles the bytecode, what they recover is already obfuscated.
- Obfuscate the source with our Python Obfuscator — rename identifiers and encrypt string literals so the extracted
.pycgives up nothing readable. - Or compile the sensitive modules to a native
.pyd/.sowith Cython — then there's no bytecode in those modules to decompile at all. - Then run PyInstaller on the protected code to get your distributable exe.
- And keep real secrets off the client — load API keys from the environment or a server, never hard-code them. See protecting API keys in Python.
See the full walk-through in convert Python to EXE and protect it. PyInstaller stays in your toolchain — it just goes *last*, after the protection.
Protect your code before you package it
A PyInstaller exe unpacks straight back to your bytecode. Obfuscate your source first — rename, encrypt strings, flatten flow — so the extracted .pyc gives up nothing. Free, in your browser.
Open the Python ObfuscatorFree tools mentioned here
Related guides
Frequently asked questions
Does PyInstaller protect or hide my source code?
No. PyInstaller bundles your compiled .pyc bytecode and the Python interpreter into one executable so it runs without Python installed — but it does not obfuscate or encrypt anything. Tools like pyinstxtractor unpack the exe back into .pyc files, which decompile to near-original source and leak string literals.
Can a PyInstaller exe be decompiled?
Yes. pyinstxtractor extracts the bundled .pyc files from the exe, and a decompiler (uncompyle6, pycdc) rebuilds near-original .py source from those. Even without decompiling, the .pyc files leak all string literals in plaintext. The exe is packaging, not protection.
What is pyinstxtractor?
pyinstxtractor is a widely-available tool that reads a PyInstaller executable and writes out every bundled .pyc file and resource. It's the standard way people recover code from a PyInstaller exe, which is why packaging alone provides no source protection.
How do I make a PyInstaller exe that actually protects my code?
Protect the source before packaging: obfuscate it (rename identifiers, encrypt strings) or compile the sensitive modules to a native .pyd/.so with Cython, then run PyInstaller on the protected code. That way, even after someone extracts and decompiles the bundled bytecode, they only recover the obfuscated version. Also keep real secrets off the client.
Is --onefile more secure than --onedir?
No, both are equally recoverable. --onefile unpacks itself to a temp folder (_MEIPASS) at runtime, and --onedir keeps the .pyc files and python DLL openly in an _internal/ folder. Either way pyinstxtractor (or just reading the folder) recovers the bytecode. The packaging mode doesn't affect protection.