Preventing Unexpected Network Connections in PyInstaller Applications
Unexpected network connections from a PyInstaller-compiled application, even without explicit network code, commonly arise from embedded library behaviors such as update checks or resource fetching. This behavior can be systematically prevented by patching core Python network modules at application startup.
The Problem
A user observed that a Python application, compiled into an executable (shortcuts.exe) with PyInstaller, initiated an outbound network connection to an Amazon-owned CDN IP address during runtime. This occurred despite the application containing no explicit network-related functionality, raising concerns about potential supply chain compromises given the application’s dependencies (ttkbootstrap>=1.10.0, pyinstaller>=6.0.0).
The Solution
To enforce strict control over network activity and prevent any unintended connections from within a PyInstaller-compiled application, core Python networking modules can be proactively disabled or modified at the application’s entry point. This ensures that any attempt by a bundled library to establish a connection will be blocked.
import socket
import ssl
import http.client
import urllib.request
import os
import sys
def disable_network_access(allow_restore=False):
"""
Disables common network functionalities by raising an IOError on connection attempts.
Args:
allow_restore (bool): If True, original functions are stored for potential restoration.
Setting this to False reduces overhead if restoration is not needed.
"""
if allow_restore:
# Store original functions to potentially restore if network is needed selectively later
global _original_socket_socket, _original_socket_connect, \
_original_ssl_wrap_socket, _original_http_client_connection, \
_original_urllib_urlopen
_original_socket_socket = socket.socket
_original_socket_connect = socket.socket.connect
_original_ssl_wrap_socket = ssl.wrap_socket
_original_http_client_connection = http.client.HTTPConnection
_original_urllib_urlopen = urllib.request.urlopen
class BlockedSocket:
def __init__(self, *args, **kwargs):
raise IOError("Network access is blocked by application policy.")
def connect(self, *args, **kwargs):
raise IOError("Network access is blocked by application policy.")
# Add other common socket methods if necessary, e.g., send, recv, bind, listen
def __getattr__(self, name):
# Attempting to access other methods on a blocked socket will also fail
raise IOError(f"Network access is blocked by application policy. Attempted method: {name}")
# Patch key network components
socket.socket = BlockedSocket
socket.socket.connect = BlockedSocket.connect # Direct patching of the method
ssl.wrap_socket = lambda *args, **kwargs: BlockedSocket() # SSL connections
http.client.HTTPConnection = BlockedSocket # HTTP connections
urllib.request.urlopen = lambda *args, **kwargs: BlockedSocket() # urlopen via urllib
# For compiled executables, PyInstaller's bootloader might handle some initial setups.
# It's good practice to call this as early as possible in your main script.
sys.stderr.write("WARNING: All network access has been programmatically disabled.\n")
# Call this function at the very beginning of your main application script
# (e.g., your_script.py before any other imports or logic)
disable_network_access()
# Your existing application code follows here.
# For example:
# import ttkbootstrap as ttk
# root = ttk.Window(themename="superhero")
# label = ttk.Label(root, text="Hello from a network-isolated app!")
# label.pack(pady=20)
# root.mainloop()
Why It Works
This solution operates by patching fundamental Python network interfaces (socket, ssl, http.client, urllib.request) at runtime.
* Low-Level Interception: Python’s networking capabilities ultimately rely on the socket module. By replacing socket.socket with a custom class that immediately raises an IOError upon instantiation or connection attempts, all higher-level network operations (HTTP, SSL, etc.) that depend on a working socket will fail.
* Comprehensive Blocking: Patching ssl.wrap_socket, http.client.HTTPConnection, and urllib.request.urlopen directly addresses common ways libraries might attempt secure (HTTPS) and unencrypted (HTTP) connections.
* Addressing Library Behaviors: Unexpected connections often stem from internal logic within bundled libraries (e.g., certifi updating its CA bundle, requests for internal version checks, or GUI libraries fetching assets like fonts/icons from CDNs). This patching approach provides a robust safeguard against such implicit network calls without requiring granular knowledge of each library’s internal workings.
* Security Posture: This method directly addresses supply chain concerns by ensuring that even if a bundled library were to contain malicious or unwanted network code, it would be prevented from establishing an external connection.
Practical Checklist
Unexpected network activity in a packaged app usually comes from dependencies, update checks, telemetry, or bundled runtime behavior. Start by reproducing the behavior in a clean environment and logging outbound connections.
- Audit dependencies and optional plugins.
- Search for update checks or analytics clients.
- Use firewall or packet capture tools during a controlled test run.
Common Mistakes
Do not assume the connection comes from your own source file only. Packaged applications include dependencies and runtime hooks that can behave differently from a plain script.