fix(moduleloader): isolate module namespaces and cache loaded modules#1132
Open
Marshall-Hallenbeck wants to merge 1 commit intomainfrom
Open
fix(moduleloader): isolate module namespaces and cache loaded modules#1132Marshall-Hallenbeck wants to merge 1 commit intomainfrom
Marshall-Hallenbeck wants to merge 1 commit intomainfrom
Conversation
The module loader used the same module name "NXCModule" for every file via spec_from_file_location(), causing sys.modules namespace collisions when multiple modules are loaded together (-M mod1 -M mod2). Each subsequent module overwrites the NXCModule class in the shared namespace, so modules loaded earlier resolve a different class than their own. This is the underlying mechanism behind multi-module ordering bugs reported in #879, #880, and #882. Changes: - Use unique module names (nxc_module_<filename>) and the modern module_from_spec() + exec_module() API instead of the deprecated load_module() to give each module its own isolated namespace - Cache loaded modules so each file is only parsed and executed once, regardless of how many targets are scanned (~60x faster than re-loading per target) - Extract shared loading logic into load_module_file() classmethod Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
14 tasks
There was a problem hiding this comment.
Pull request overview
Fixes multi-module ordering bugs by ensuring each loaded module file gets an isolated import namespace (avoiding sys.modules["NXCModule"] collisions) and by caching loaded module files to avoid repeated parsing/execution across many targets.
Changes:
- Introduces a class-level module cache and a shared
load_module_file()loader usingmodule_from_spec()+exec_module(). - Updates
load_module()andget_module_info()to use the shared loader (and benefit from isolation + caching).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Description
The module loader uses
spec_from_file_location("NXCModule", module_path)with the same module name"NXCModule"for every module file. When multiple modules are loaded via-M, each subsequent module overwrites theNXCModuleclass in the sharedsys.modules["NXCModule"]namespace. This means modules loaded earlier resolve a different module'sNXCModuleclass when referencing it — causing attribute errors, wrong method calls, or other subtle breakage depending on which module loads last.This is the underlying mechanism behind the multi-module ordering bugs reported in #879, #880, and #882 — all of which describe symptoms where the order of
-Mflags changes behavior or causes failures.Changes:
Isolated namespaces — Each module file now gets a unique name (
nxc_module_<filename>) and is loaded via the modernmodule_from_spec()+exec_module()API instead of the deprecatedload_module(). This gives each module its own namespace — no more cross-module pollution.Module caching — Loaded modules are cached in a class-level dict so each file is only parsed and executed once, regardless of how many targets are scanned. This is ~60x faster than the old code for multi-target scans:
load_modulewithsys.modules)exec_module)DRY — Extracted shared loading logic into
load_module_file()classmethod, used by bothload_module()andget_module_info().AI disclosure: Claude Code (Claude Opus 4.6) was used to assist with root cause analysis, benchmarking, and drafting the fix. The bug was discovered during a real pentest scan, root cause was traced and verified by human and AI together, and the fix was human-reviewed and tested on live targets.
Type of change
Setup guide for the review
How to demonstrate the namespace collision (without this fix):
Tested on:
Screenshots (if appropriate):
N/A — this is an internal loader change with no user-visible output difference (other than fixing the errors).
Checklist:
poetry run ruff check ., use--fixto automatically fix what it can)tests/e2e_commands.txtfile if necessary (new modules or features are required to be added to the e2e tests)