Repository navigation
LANG-145: confine execution.python.path to the plugin directory - #189
Merged
Merged
Conversation
execution.python.path is plugin-authored manifest data. The component loader joined it to the manifest's directory, stripped a trailing .py, turned separators into dots and imported the result, with sys.path.append(os.getcwd()) left behind permanently. The '..' traversal the audit described does not actually import outside the plugin: '../x.py' becomes '<rel_dir>....x', whose empty dotted segments raise ModuleNotFoundError. But the resolver had no validation at all, so the behaviour depended on that accident, and the permanent cwd append let one plugin shadow modules for every plugin loaded after it. _resolve_component_module now resolves the target against the plugin directory and rejects empty/NUL paths, absolute paths, drive letters, any '..' component, and anything that resolves outside the plugin dir. Import is performed with the plugin directory inserted on sys.path only for the duration of the import. Tests: 11 cases - rejection forms, a component inside the plugin still importing, the module path without a .py suffix, sys.path left byte-identical after both success and ModuleNotFoundError, and module cache isolation between cases. Co-authored-by: multica-agent <github@multica.ai>
… tests Co-authored-by: multica-agent <github@multica.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
execution.python.pathis plugin-authored manifest data, so it is untrusted. The component loader joined it to the manifest's directory, stripped a trailing.py, converted separators to dots and imported — withsys.path.append(os.getcwd())added on every call and never removed.What I found when I tested it
The traversal the audit reported does not actually import outside the plugin.
../outside.pybecomes<rel_dir>....outside, and the empty dotted segments raiseModuleNotFoundError; the same for../../and absolute paths (a leading/becomes a leading.). So there was no exploitable load-arbitrary-module path.That means the behaviour rested on an accident of string replacement, and two real defects remained:
sys.path.append(os.getcwd())lets one plugin shadow modules for every plugin loaded afterwards.Fix
_resolve_component_moduleresolves the target against the plugin directory and rejects:..componentcommonpathcheck)Import now inserts the plugin directory on
sys.pathonly for the duration of the import and removes it in afinally, so a failure leavessys.pathuntouched.Tests
11 cases in
tests/api/definition/test_manifest_component_path.py: every rejection form; a component inside the plugin still imports; a path without the.pysuffix;sys.pathbyte-identical after both success andModuleNotFoundError; module-cache isolation between cases.