fix(scanner): reject spoofed wardline decorators#26
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 017f82c7a7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| module := module_dotted_name( | ||
| path.relative_to(root).as_posix() if path.is_relative_to(root) else path.as_posix() | ||
| ) |
There was a problem hiding this comment.
Account for configured source roots when detecting shadows
When a project uses a non-src source root such as source_roots: ["lib"], a local lib/wardline/decorators/__init__.py is discovered as module lib.wardline.decorators here, so _project_shadows_wardline() returns false and from wardline.decorators import trusted in lib/app.py is still anchored as Wardline's real @trusted. That leaves the spoofing false-green this change is meant to close for any configured import root other than ./src.
Useful? React with 👍 / 👎.
| project_modules = frozenset( | ||
| module | ||
| for path in stage_input.files |
There was a problem hiding this comment.
Detect excluded shadow packages before trusting decorators
Because project_modules is derived only from stage_input.files, a repo can hide a local wardline/decorators/__init__.py with an exclude pattern (or otherwise omit it from discovery) while app.py is still scanned. At runtime from wardline.decorators import trusted in app.py resolves to that local package, but the scanner never sees the shadow here and still anchors @trusted as Wardline's builtin marker, preserving the spoofing false-green this patch is intended to remove.
Useful? React with 👍 / 👎.
| for bt in self._boundary_types: | ||
| if last != bt.canonical_name or not fqn.startswith(bt.module_prefix + "."): | ||
| if bt.builtin: | ||
| if wardline_shadowed or not _is_builtin_decorator_fqn(fqn, bt.canonical_name, bt.module_prefix): |
There was a problem hiding this comment.
Require builtin decorators to be import-proven
In a scanned module that defines a local object named wardline (for example a class or namespace with decorators.trusted) and then uses @wardline.decorators.trusted, there is no project module shadow to trip wardline_shadowed, but _resolve_decorator_fqn() still returns the exact builtin-looking string and this branch anchors the function as trusted. The decorator root was never proven to come from an import of Wardline's package, so local symbol spoofing still produces the false-green this hardening is trying to prevent.
Useful? React with 👍 / 👎.
Close a HIGH-severity false-green. The decorator taint provider trusted any FQN whose prefix was a builtin marker module and whose final segment was a known marker name, without verifying it resolved to Wardline's real package. A scanned project shipping its own `wardline/decorators` (or `loom_markers`) no-op could spoof `@trusted`/`@trust_boundary`, launder untrusted data up to a trusted tier, and suppress real taint->sink findings. - Builtin markers now match ONLY exact exports (`P.<name>`, `P.trust.<name>`); nested-path spoofs are rejected. Custom grammar markers keep prefix+name. - Fail closed for builtin markers under any marker root the scanned project shadows; shadow roots are derived dynamically from BUILTIN_BOUNDARY_TYPES (`wardline` AND `loom_markers`), not hardcoded. - Thread a per-root shadow-aware provider fingerprint through the parse stage into both dirty-detection and the resolver summary cache, so a TRUSTED summary computed under a non-shadowed root cannot be reused under a shadowed one (cross-root cache poisoning). - Document the Clarion fact-key residual: the content-hash freshness gate is a cross-tool contract Clarion independently recomputes, so a Wardline-private shadow bit cannot be folded in without a Clarion read-path change. Opt-in (`--clarion-url`) path, not the scan gate. Reimplements and generalizes the fix proposed in #26 (which closed only the `wardline` root and now conflicts with main). Full suite green (2390 passed), ruff + mypy clean, self-scan unchanged (0 new defects). Co-authored-by: John Morrissey <john@wardline.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Fixed ourselves in The vulnerability is real and HIGH-severity — I reproduced the false-green live on We wrote our own fix rather than merge this for two reasons:
Also addressed beyond this PR: the Clarion taint-fact freshness key is whole-file-content-only and can't see shadow state, so the MCP Verification on #27: full suite 2390 passed, ruff + mypy clean, both spoofs defeated in a live repro while a real boundary still suppresses, and wardline's own self-scan is unchanged (0 new defects). Thanks for catching this — closing in favour of #27. |
Motivation
wardline.decorators.*and could be spoofed by a scanned project that defined a localwardline.decoratorspackage, causing unsafe functions to be anchored as trusted and producing false-green results.wardlinepackage.Description
DecoratorTaintSourceProviderto exact known exports (wardline.decorators.<name>andwardline.decorators.trust.<name>) and reject prefix+final-segment matches for builtins; custom grammar markers retain the documented prefix+name behavior.SeedContextviaproject_modules: frozenset[str]and make the provider fail-closed if the scanned project defines awardlinemodule/package that could shadow the real marker package.run_parse_project_stage) into analyzer resolution so summary-cache keys include whetherwardlineis shadowed, preventing unsafe cache reuse across shadowed/unshadowed scan roots (fingerprint_for_project).wardline.decoratorsshadowing, and the implementation-module export form, and update pipeline tests to assert provider-fingerprint propagation.Testing
python -m compileall(checked files),PYTHONPATH=src python -m ruff check, andPYTHONPATH=src python -m mypyall passed.PYTHONPATH=src python -m pytest tests/unit/scanner/taint/test_decorator_provider.py -q -k 'not star_imported_trust_boundary_fires_end_to_end'passed (29 passed, 1 deselected) and targeted pipeline/provider tests passed (tests/unit/scanner/test_pipeline.py,tests/unit/scanner/taint/test_provider_seedcontext.py, and several focused provider tests all passed).test_star_imported_trust_boundary_fires_end_to_end) still fails in this environment only because PyYAML (wardline[scanner]extra) is not installed; this is a pre-existing dependency-related limitation and not caused by the changes.Codex Task