From 48bf4fd82aea7075c4e7576fe301938099ebdd90 Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 11 Mar 2026 16:01:47 +0000 Subject: [PATCH 1/3] Python: Add test for missing relative import in namespace packages --- .../pkg/caller.py | 5 +++ .../pkg/helper.py | 2 ++ .../test.expected | 1 + .../test.ql | 35 +++++++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 python/ql/test/experimental/import-resolution-namespace-relative/pkg/caller.py create mode 100644 python/ql/test/experimental/import-resolution-namespace-relative/pkg/helper.py create mode 100644 python/ql/test/experimental/import-resolution-namespace-relative/test.expected create mode 100644 python/ql/test/experimental/import-resolution-namespace-relative/test.ql diff --git a/python/ql/test/experimental/import-resolution-namespace-relative/pkg/caller.py b/python/ql/test/experimental/import-resolution-namespace-relative/pkg/caller.py new file mode 100644 index 000000000000..f30065eec990 --- /dev/null +++ b/python/ql/test/experimental/import-resolution-namespace-relative/pkg/caller.py @@ -0,0 +1,5 @@ +from . import helper + +def use_relative(): + tainted = source() + helper.process(tainted) diff --git a/python/ql/test/experimental/import-resolution-namespace-relative/pkg/helper.py b/python/ql/test/experimental/import-resolution-namespace-relative/pkg/helper.py new file mode 100644 index 000000000000..43167b8cfa7d --- /dev/null +++ b/python/ql/test/experimental/import-resolution-namespace-relative/pkg/helper.py @@ -0,0 +1,2 @@ +def process(value): + sink(value) #$ MISSING: prints=source diff --git a/python/ql/test/experimental/import-resolution-namespace-relative/test.expected b/python/ql/test/experimental/import-resolution-namespace-relative/test.expected new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/ql/test/experimental/import-resolution-namespace-relative/test.expected @@ -0,0 +1 @@ + diff --git a/python/ql/test/experimental/import-resolution-namespace-relative/test.ql b/python/ql/test/experimental/import-resolution-namespace-relative/test.ql new file mode 100644 index 000000000000..f826c02e423c --- /dev/null +++ b/python/ql/test/experimental/import-resolution-namespace-relative/test.ql @@ -0,0 +1,35 @@ +import python +import semmle.python.dataflow.new.DataFlow +import semmle.python.dataflow.new.TaintTracking +import utils.test.InlineExpectationsTest + +private module TestConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { + node.(DataFlow::CallCfgNode).getFunction().asCfgNode().(NameNode).getId() = "source" + } + + predicate isSink(DataFlow::Node node) { + exists(DataFlow::CallCfgNode call | + call.getFunction().asCfgNode().(NameNode).getId() = "sink" and + node = call.getArg(0) + ) + } +} + +private module TestFlow = TaintTracking::Global; + +module FlowTest implements TestSig { + string getARelevantTag() { result = "prints" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(DataFlow::Node sink | + TestFlow::flow(_, sink) and + tag = "prints" and + location = sink.getLocation() and + value = "source" and + element = sink.toString() + ) + } +} + +import MakeTest From e16bb226c08c8131e10748d7e4990a03e4d0220f Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 11 Mar 2026 23:02:09 +0000 Subject: [PATCH 2/3] Python: Fix resolution of relative imports from namespace packages The fix may look a bit obscure, so here's what's going on. When we see `from . import helper`, we create an `ImportExpr` with level equal to 1 (corresponding to the number of dots). To resolve such imports, we compute the name of the enclosing package, as part of `ImportExpr.qualifiedTopName()`. For this form of import expression, it is equivalent to `this.getEnclosingModule().getPackageName()`. But `qualifiedTopName` requires that `valid_module_name` holds for its result, and this was _not_ the case for namespace packages. To fix this, we extend `valid_module_name` to include the module names of _any_ folder, not just regular package (which are the ones where there's a `__init__.py` in the folder). Note that this doesn't simply include all folders -- only the ones that result in valid module names in Python. --- python/ql/lib/semmle/python/Import.qll | 4 ++++ .../import-resolution-namespace-relative/pkg/helper.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/python/ql/lib/semmle/python/Import.qll b/python/ql/lib/semmle/python/Import.qll index e8a7facccad3..2f7fae955399 100644 --- a/python/ql/lib/semmle/python/Import.qll +++ b/python/ql/lib/semmle/python/Import.qll @@ -17,6 +17,10 @@ private predicate valid_module_name(string name) { exists(Module m | m.getName() = name) or exists(Builtin cmod | cmod.getClass() = Builtin::special("ModuleType") and cmod.getName() = name) + or + // Namespace packages may not have a corresponding Module entity, + // but their names are still valid for the purpose of import resolution. + name = moduleNameFromFile(any(Folder f)) } /** An artificial expression representing an import */ diff --git a/python/ql/test/experimental/import-resolution-namespace-relative/pkg/helper.py b/python/ql/test/experimental/import-resolution-namespace-relative/pkg/helper.py index 43167b8cfa7d..b9407161e08b 100644 --- a/python/ql/test/experimental/import-resolution-namespace-relative/pkg/helper.py +++ b/python/ql/test/experimental/import-resolution-namespace-relative/pkg/helper.py @@ -1,2 +1,2 @@ def process(value): - sink(value) #$ MISSING: prints=source + sink(value) #$ prints=source From 3ee369b7109fc4debf92272fbc9554e8b680a4cb Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 11 Mar 2026 23:14:57 +0000 Subject: [PATCH 3/3] Python: Add change note --- .../2026-03-11-fix-unresolved-relative-imports.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 python/ql/lib/change-notes/2026-03-11-fix-unresolved-relative-imports.md diff --git a/python/ql/lib/change-notes/2026-03-11-fix-unresolved-relative-imports.md b/python/ql/lib/change-notes/2026-03-11-fix-unresolved-relative-imports.md new file mode 100644 index 000000000000..15290fb3d669 --- /dev/null +++ b/python/ql/lib/change-notes/2026-03-11-fix-unresolved-relative-imports.md @@ -0,0 +1,5 @@ +--- +category: fix +--- + +- Fixed the resolution of relative imports such as `from . import helper` inside namespace packages (directories without an `__init__.py` file), which previously did not work correctly, leading to missing flow.