The portmod sandbox allows safe access to dangerous functions such as shutil.rmtree while still allowing pybuilds to be as pythonic as possible.
This is achieved through the use of RestrictedPython and custom import code which provides stripped-down copies of modules for use within pybuilds, using wrappers with an io guard for modules that allow access to the filesystem. Binaries are only allowed to be executed through the use of a sandbox program such as bubblewrap on Linux and sandbox-exec on OSX.
In general, the following is restricted:
File I/O in the global scope. All file I/O must be within functions in the Mod class.
File I/O outside the build directory in src_unpack and can_update_live.
Network access outside src_unpack and can_update_live
File writes outside the build directory in all scopes
The idea is that this prevents:
Poorly written code from accidentally performing dangerous actions such as deleting your files.
Malicious actors from creating a seemingly benign third-party repository and causing either deliberate damage to your system or stealing your personal information when you try to install or update mods from that repository.
Additionally, the following restrictions apply to the pybuild code:
You may only access the following modules: portmod, portmod.pybuild, portmod.pybuild.modinfo, filecmp, os, os.path, sys, shutil, fnmatch, re, json, typing, and the pyclass module and its submodules (noting that pyclass modules are subject to the same restrictions as pybuilds). Support for other modules can be added on request.
Use of the str.format function is banned. This is known to be unsafe and is disabled by RestrictedPython by default. It is encouraged to used f-strings instead.
Access to attributes that begin with underscores is banned.
Use of the super function is banned. RestrictedPython has disabled it for no particular reason other than not having enough knowledge of its safety. super().__init__(self) is automatically called in any Mod class that overrides __init__ (due to __init__ not being accessible due to the previous point. For other functions it is recommended to use SuperClass.function(self) instead.
Use of builtins that allow arbitrary code execution is banned. This includes exec, compile, eval, etc.
Direct file access through open and file descriptors.
All external executable calls are sandboxed using a platform-specific sandbox command. This prevents filesystem write access outside the build directory and prevents filesystem read access until all potentially-unsafe network access has been completed (i.e. prevents a malicious pybuild from scanning your system and uploading data to a remote server).
As there is no known (to us) open-source sandbox tool for Windows, we instead provide two environment variables that you can use to store a sandbox command to be used to protect binaries. You are encouraged to install a command-line sandbox tool (if you find an open-source tool, please let us know!) and set the following environment variables:
SANDBOX_COMMAND_NETWORK: This should be a sandbox command that has access to the network, but no access to the filesystem outside TMP_DIR, the temporary directory used during installation of mods (see omwmerge --info for the location of TMP_DIR).
SANDBOX_COMMAND: This should be a sandbox command that noes not have access to the network, but has read access to the filesystem outside of TMP_DIR (at the very least, any files related to your game, including portmod files). It should not provide write access outside of TMP_DIR.
WARNING: Setting these environment variables in a way that differs from what is described may allow pybuilds access to your system in a way that is undesirable and could be exploited by bad actors (or mistakes in code) to cause irreparable damage to your system or gain access to your personal information
If you want to verify that your sandbox setup is correct, either run python setup.py test manually (requires that you have downloaded the portmod sources including setup.py), or reinstall portmod with the --install-option test passed to pip.