Repository navigation
fix(core): resolve every configured module path under one policy (#490) - #563
Merged
mlieberman85 merged 2 commits intoOct 7, 2026
Merged
Conversation
Section 6.5 described a prefix whitelist "discovered from entry points" that the code never implemented, and did not cover the MCP tool loader, which imported any module named in [mcp.tools]. State the contract the fix implements: one resolver for every module:attribute import, a top-level package of darnit or an installed implementation's entry point module, refusal before import, and refused tools reported at server start. Refs darnitdevorg#490 Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Michael Lieberman <mlieberman85@gmail.com>
ToolRegistry.load_handler, the path that loads [mcp.tools] handlers at server start, imported any module:function string with no check, while HandlerRegistry and the two adapter registries each kept a hardcoded prefix tuple that missed darnit_csl, darnit_gittuf, darnit_reproducibility and darnit_hello. The shipped community-spec tool loaded only through the unchecked path. All four now call darnit.core.handlers.resolve_module_path. It refuses a malformed path or a top-level package other than darnit or the module of a discovered darnit.implementations entry point, before importing. The allowed set is recorded by discovery and reset with its cache. A refusal raises HandlerImportRefused (a ValueError) naming the path and allowed packages; HandlerRegistry.get_handler raises it rather than returning None, and the server factory logs the refused tool at ERROR and does not register it. Fixes darnitdevorg#490 Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Michael Lieberman <mlieberman85@gmail.com>
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.
Summary
module:functionstrings from configuration were turned into imports in two places, with two different policies:HandlerRegistrychecked a hard-coded, out-of-date prefix list. It rejecteddarnit_csl,darnit_gittuf,darnit_reproducibilityanddarnit_hello.ToolRegistry.load_handler, which loads[mcp.tools.*]at server start, imported any module. Before this change, a tool spec withhandler = "os:system"registered as an MCP tool.With this change:
darnit.core.handlers.resolve_module_pathcontains the onlyimportlib.import_modulecall inpackages/darnit/src. A test enforces that.darnit, or the module of an implementation that discovery accepted from thedarnit.implementationsentry points (e.g.darnit_csl:registergivesdarnit_csl). A plugin skipped by discovery or verification is excluded. The path must have exactly one:, and every part must be an identifier.HandlerImportRefused(aValueError) names the path and the allowed packages, and is logged at WARNING. The server factories skip a refused tool and log an ERROR naming it.ALLOWED_MODULE_PREFIXEStuples are gone, including the copies in the dead adapter code.Docs:
docs/architecture/framework-design.mdsection 6.5 was updated first, with two new scenarios.THREAT_MODEL.md: the dynamic-import findings are collapsed into one entry that describes the control as it now holds.docs/IMPLEMENTATION_GUIDE.mdanddocs/SECURITY_GUIDE.mdno longer tell plugin authors to extend the prefix list.Fixes #490
Type of Change
HandlerRegistry.get_handlernow raises for a refused path instead of returningNone; the CHANGELOG lists this under Removed and Security.Framework Changes Checklist
docs/architecture/framework-design.md) if behavior changeduv run python scripts/validate_sync.py --verboseand it passesTesting
uv run pytest tests/ -v): 5162 passed, 29 skippedtests/darnit/core/test_module_path_policy.py(31 cases), plus factory and registry testsuv run ruff check .)New coverage:
os:system,subprocess:run,builtins:eval, unknown packages, and malformed paths.TestShippedFrameworkToolsstarts every installed framework's server and checks that each declared tool registers (baseline 18 of 18, CSL 3 of 3, and so on).AI assistance
Claude (Claude Code, claude-opus-5-5) made this change: spec, code, and tests. This description was also drafted with Claude. Commits carry an
Assisted-by: Claude:claude-opus-5-5trailer.Additional Notes
packages/darnit-testchecks/testchecks.tomldeclares a Python adapter module (darnit_testchecks.adapters.builtin) that this policy would refuse, because testchecks registers underdarnit.frameworks, notdarnit.implementations. Nothing loads it through these paths today; the adapter loaders have no production caller. Deleting those loaders under #487 is better than widening the policy.🤖 Generated with Claude Code