Skip to content

Commit 469764d

Browse files
committed
update for dynamic imports
1 parent 4d8fd2f commit 469764d

1 file changed

Lines changed: 53 additions & 0 deletions

File tree

constructs/dynamicimports.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Dynamic imports are especially dangerous if you cannot validate upfront what is
99
Dynamic imports can be achieved using:
1010
* `__import__`: This built-in function **SHOULD** never be used. It is an advanced, low-level function that is not needed in everyday Python programming.
1111
* `importlib.import_module()`: If dynamic imports are unavoidable, this function is the preferred approach. However, its use must be validated upfront.
12+
* `importlib.util.spec_from_file_location` in combination with `importlib.util.module_from_spec`
1213

1314
## Security concerns
1415

@@ -40,6 +41,18 @@ module = importlib.import_module("module_name")
4041
Avoid using `__import__()` entirely.
4142
:::
4243

44+
## Module imports in Python Plugin systems
45+
46+
From a [security-by-design](https://nocomplexity.github.io/securitybydesign/) perspective, utilizing `importlib.util.spec_from_file_location()` alongside `module_from_spec()` to load plugins introduces severe architectural security risks. This mechanism operates at a low level, bypassing Python’s standard, sandboxed import restrictions and allowing the direct execution of arbitrary code from any accessible file path on the disk.
47+
48+
If the file paths or module specifications are influenced by untrusted input—such as user-controlled configuration files or unvalidated directory paths—an attacker can exploit this to achieve Remote Code Execution (RCE) by tricking the application into importing malicious scripts.
49+
50+
51+
Rather than pointing directly to unpredictable locations on disk, modern Python architectures should leverage **namespace packages** or the standard **`importlib.metadata` entry points API**. By registering plugins via package metadata, the application shifts from a dangerous "pull-from-disk" model to a secure, declarative system where only explicitly installed distribution packages can be loaded.
52+
53+
54+
55+
4356
## Mitigations
4457

4558
There is **always** a security risk when `importlib.import_module()` is used. No mitigation eliminates this risk entirely — it can only be reduced.
@@ -84,6 +97,16 @@ module.system("rm -rf /") # Potentially catastrophic
8497
- Attacker can import any standard library or installed module
8598
- Attacker can potentially call dangerous functions after import
8699

100+
### The Vulnerable Pattern for a plugin system
101+
102+
```python
103+
# SECURITY RISK: Avoid this low-level dynamic loading pattern
104+
spec = importlib.util.spec_from_file_location(plugin_name, unsafe_file_path)
105+
module = importlib.util.module_from_spec(spec)
106+
spec.loader.exec_module(module) # Arbitrary code execution occurs here
107+
108+
```
109+
87110

88111
### No Dynamic Imports (Recommended)
89112

@@ -147,10 +170,40 @@ except ValueError as e:
147170
| **Validate function access** | Do not expose entire modules — restrict access to specific safe functions |
148171
| **Audit all imports** | Use static analysis tools to detect and review all dynamic import usage |
149172

173+
### Security and Python plugin systems
174+
175+
176+
| Method | Dynamic discovery | Loads arbitrary files | Recommended |
177+
| -------------------------------------------- | ----------------- | --------------------- | ----------- |
178+
| `importlib.metadata.entry_points()` | Yes | No | ⭐⭐⭐⭐⭐ |
179+
| `importlib.import_module()` | Limited | No | ⭐⭐⭐⭐☆ |
180+
| `pkgutil.iter_modules()` + `import_module()` | Yes | No | ⭐⭐⭐⭐☆ |
181+
| Namespace packages | Yes | No | ⭐⭐⭐⭐☆ |
182+
| `spec_from_file_location()` | Yes | **Yes** | ⭐⭐☆☆☆ |
183+
| `SourceFileLoader` | Yes | **Yes** | ⭐⭐☆☆☆ |
184+
| `__import__()` | No | No | ⭐⭐⭐☆☆ |
185+
186+
187+
188+
For new applications, the most robust and secure options are:
189+
190+
1. `importlib.metadata.entry_points()` when plugins are installed as Python packages. This leverages Python's packaging ecosystem and avoids arbitrary file loading.
191+
2. `pkgutil.iter_modules()` + `importlib.import_module()` when plugins reside in a known package within your application. This provides automatic discovery while still using Python's standard import machinery.
192+
3. Reserve `importlib.util.spec_from_file_location()` for cases where you genuinely need to load modules from arbitrary file paths (for example, user-provided scripts in a controlled environment). If you use it, ensure the plugin directory is trusted, validate or authenticate plugin files (for example, using signatures or hashes), and avoid passing user-controlled paths directly to the loader.
193+
194+
From a security perspective, `spec_from_file_location()` and related APIs (`module_from_spec()`, `exec_module()`) are **always** worth reviewing as this methods can bypass the normal import mechanism and can execute arbitrary Python files. By contrast, `import_module()`, `pkgutil.iter_modules()`, and `importlib.metadata.entry_points()` generally represent a lower-risk patterns because they work within Python's standard import and packaging systems.
195+
196+
:::{tip}
197+
Never trust an import system in Python that has the ability to load arbitrary files!
198+
:::
199+
200+
150201
## References
151202

152203
- [Python `importlib.import_module()` documentation](https://docs.python.org/3/library/importlib.html#importlib.import_module)
153204
- [Python `__import__()` documentation (legacy)](https://docs.python.org/3/library/functions.html#import__)
205+
- [Implicit Execution of Arbitrary Code via Automatic `tools.py` Loading ](https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-2g3w-cpc4-chr4)
206+
- [CVE-2026-40156](https://nvd.nist.gov/vuln/detail/CVE-2026-40156)
154207
- [CWE-94: Improper Control of Generation of Code ('Code Injection')](https://cwe.mitre.org/data/definitions/94.html)
155208
- [CWE-706: Use of Incorrectly-Resolved Name or Reference](https://cwe.mitre.org/data/definitions/706.html)
156209
```

0 commit comments

Comments
 (0)