From 8e63c2870371035d3ce1a51d290a97bc9e53f063 Mon Sep 17 00:00:00 2001 From: yoff Date: Wed, 10 Dec 2025 01:37:39 +0100 Subject: [PATCH 1/8] python: remove barrier that can be expressed in MaD --- .../lib/semmle/python/frameworks/Django.qll | 32 ------------------- .../CWE-601-UrlRedirect/UrlRedirect.expected | 3 ++ 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/python/ql/lib/semmle/python/frameworks/Django.qll b/python/ql/lib/semmle/python/frameworks/Django.qll index 4aa5776ad54b..ee0ed4a84dd0 100644 --- a/python/ql/lib/semmle/python/frameworks/Django.qll +++ b/python/ql/lib/semmle/python/frameworks/Django.qll @@ -2965,38 +2965,6 @@ module PrivateDjango { override predicate csrfEnabled() { decoratorName in ["csrf_protect", "requires_csrf_token"] } } - private predicate djangoUrlHasAllowedHostAndScheme( - DataFlow::GuardNode g, ControlFlowNode node, boolean branch - ) { - exists(API::CallNode call | - call = - API::moduleImport("django") - .getMember("utils") - .getMember("http") - .getMember("url_has_allowed_host_and_scheme") - .getACall() and - g = call.asCfgNode() and - node = call.getParameter(0, "url").asSink().asCfgNode() and - branch = true - ) - } - - /** - * A call to `django.utils.http.url_has_allowed_host_and_scheme`, considered as a sanitizer-guard for URL redirection. - * - * See https://docs.djangoproject.com/en/4.2/_modules/django/utils/http/ - */ - private class DjangoAllowedUrl extends UrlRedirect::Sanitizer { - DjangoAllowedUrl() { - this = DataFlow::BarrierGuard::getABarrierNode() - } - - override predicate sanitizes(UrlRedirect::FlowState state) { - // sanitize all flow states - any() - } - } - // --------------------------------------------------------------------------- // Templates // --------------------------------------------------------------------------- diff --git a/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/UrlRedirect.expected b/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/UrlRedirect.expected index 551299a64dc4..d7c891b46349 100644 --- a/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/UrlRedirect.expected +++ b/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/UrlRedirect.expected @@ -52,6 +52,7 @@ edges | test.py:81:17:81:46 | ControlFlowNode for Attribute() | test.py:81:5:81:13 | ControlFlowNode for untrusted | provenance | | | test.py:82:5:82:10 | ControlFlowNode for unsafe | test.py:83:21:83:26 | ControlFlowNode for unsafe | provenance | | | test.py:90:5:90:13 | ControlFlowNode for untrusted | test.py:93:18:93:26 | ControlFlowNode for untrusted | provenance | | +| test.py:90:5:90:13 | ControlFlowNode for untrusted | test.py:95:25:95:33 | ControlFlowNode for untrusted | provenance | | | test.py:90:17:90:23 | ControlFlowNode for request | test.py:90:17:90:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | test.py:90:17:90:28 | ControlFlowNode for Attribute | test.py:90:17:90:46 | ControlFlowNode for Attribute() | provenance | dict.get | | test.py:90:17:90:46 | ControlFlowNode for Attribute() | test.py:90:5:90:13 | ControlFlowNode for untrusted | provenance | | @@ -122,6 +123,7 @@ nodes | test.py:90:17:90:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | | test.py:90:17:90:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | test.py:93:18:93:26 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:95:25:95:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | | test.py:111:5:111:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | | test.py:111:17:111:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | | test.py:111:17:111:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | @@ -148,6 +150,7 @@ subpaths | test.py:76:21:76:26 | ControlFlowNode for unsafe | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:76:21:76:26 | ControlFlowNode for unsafe | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:83:21:83:26 | ControlFlowNode for unsafe | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:83:21:83:26 | ControlFlowNode for unsafe | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:93:18:93:26 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:93:18:93:26 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:95:25:95:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:95:25:95:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:114:25:114:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:114:25:114:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:140:25:140:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:140:25:140:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:148:25:148:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:148:25:148:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | From fb7a1c73fc3711e268fcb4a07bdc44037d5b7174 Mon Sep 17 00:00:00 2001 From: yoff Date: Wed, 10 Dec 2025 01:43:14 +0100 Subject: [PATCH 2/8] python: add machinery for MaD barriers and reinstate previously removed barrier now as a MaD row --- .../semmle/python/frameworks/Django.model.yml | 6 ++ .../data/internal/ApiGraphModels.qll | 66 ++++++++++++++++++- .../internal/ApiGraphModelsExtensions.qll | 20 ++++++ .../frameworks/data/internal/empty.model.yml | 10 +++ .../dataflow/UrlRedirectCustomizations.qll | 20 ++++++ .../CWE-601-UrlRedirect/UrlRedirect.expected | 3 - 6 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 python/ql/lib/semmle/python/frameworks/Django.model.yml diff --git a/python/ql/lib/semmle/python/frameworks/Django.model.yml b/python/ql/lib/semmle/python/frameworks/Django.model.yml new file mode 100644 index 000000000000..4e472af6c117 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Django.model.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/python-all + extensible: barrierGuardModel + data: + - ['django', 'Member[utils].Member[http].Member[url_has_allowed_host_and_scheme].Argument[0,url:]', "true", 'url-redirection'] diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll index 80ec45a3cf17..ee2cd8f57bfe 100644 --- a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll +++ b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll @@ -344,6 +344,26 @@ private predicate sinkModel(string type, string path, string kind, string model) ) } +/** Holds if a barrier model exists for the given parameters. */ +private predicate barrierModel(string type, string path, string kind, string model) { + // No deprecation adapter for barrier models, they were not around back then. + exists(QlBuiltins::ExtensionId madId | + Extensions::barrierModel(type, path, kind, madId) and + model = "MaD:" + madId.toString() + ) +} + +/** Holds if a barrier guard model exists for the given parameters. */ +private predicate barrierGuardModel( + string type, string path, string branch, string kind, string model +) { + // No deprecation adapter for barrier models, they were not around back then. + exists(QlBuiltins::ExtensionId madId | + Extensions::barrierGuardModel(type, path, branch, kind, madId) and + model = "MaD:" + madId.toString() + ) +} + /** Holds if a summary model `row` exists for the given parameters. */ private predicate summaryModel( string type, string path, string input, string output, string kind, string model @@ -400,6 +420,8 @@ predicate isRelevantType(string type) { ( sourceModel(type, _, _, _) or sinkModel(type, _, _, _) or + barrierModel(type, _, _, _) or + barrierGuardModel(type, _, _, _, _) or summaryModel(type, _, _, _, _, _) or typeModel(_, type, _) ) and @@ -427,6 +449,8 @@ predicate isRelevantFullPath(string type, string path) { ( sourceModel(type, path, _, _) or sinkModel(type, path, _, _) or + barrierModel(type, path, _, _) or + barrierGuardModel(type, path, _, _, _) or summaryModel(type, path, _, _, _, _) or typeModel(_, type, path) ) @@ -745,6 +769,32 @@ module ModelOutput { ) } + /** + * Holds if a barrier model contributed `barrier` with the given `kind`. + */ + cached + API::Node getABarrierNode(string kind, string model) { + exists(string type, string path | + barrierModel(type, path, kind, model) and + result = getNodeFromPath(type, path) + ) + } + + /** + * Holds if a barrier model contributed `barrier` with the given `kind`. + */ + cached + API::Node getABarrierGuardNode(string kind, boolean branch, string model) { + exists(string type, string path, string branch_str | + branch = true and branch_str = "true" + or + branch = false and branch_str = "false" + | + barrierGuardModel(type, path, branch_str, kind, model) and + result = getNodeFromPath(type, path) + ) + } + /** * Holds if a relevant summary exists for these parameters. */ @@ -787,15 +837,27 @@ module ModelOutput { private import codeql.mad.ModelValidation as SharedModelVal /** - * Holds if a CSV source model contributed `source` with the given `kind`. + * Holds if an external model contributed `source` with the given `kind`. */ API::Node getASourceNode(string kind) { result = getASourceNode(kind, _) } /** - * Holds if a CSV sink model contributed `sink` with the given `kind`. + * Holds if an external model contributed `sink` with the given `kind`. */ API::Node getASinkNode(string kind) { result = getASinkNode(kind, _) } + /** + * Holds if an external model contributed `barrier` with the given `kind`. + */ + API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) } + + /** + * Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`. + */ + API::Node getABarrierGuardNode(string kind, boolean branch) { + result = getABarrierGuardNode(kind, branch, _) + } + private module KindValConfig implements SharedModelVal::KindValidationConfigSig { predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) } diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsExtensions.qll b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsExtensions.qll index 3f38c498f324..2a644aabb95d 100644 --- a/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsExtensions.qll +++ b/python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsExtensions.qll @@ -20,6 +20,26 @@ extensible predicate sourceModel( */ extensible predicate sinkModel(string type, string path, string kind, QlBuiltins::ExtensionId madId); +/** + * Holds if the value at `(type, path)` should be seen as a barrier + * of the given `kind` and `madId` is the data extension row number. + */ +extensible predicate barrierModel( + string type, string path, string kind, QlBuiltins::ExtensionId madId +); + +/** + * Holds if the value at `(type, path)` should be seen as a barrier guard + * of the given `kind` and `madId` is the data extension row number. + * `path` is assumed to lead to a parameter of a call (possibly `self`), and + * the call is guarding the parameter. + * `branch` is either `true` or `false`, indicating which branch of the guard + * is protecting the parameter. + */ +extensible predicate barrierGuardModel( + string type, string path, string branch, string kind, QlBuiltins::ExtensionId madId +); + /** * Holds if in calls to `(type, path)`, the value referred to by `input` * can flow to the value referred to by `output` and `madId` is the data diff --git a/python/ql/lib/semmle/python/frameworks/data/internal/empty.model.yml b/python/ql/lib/semmle/python/frameworks/data/internal/empty.model.yml index ea9b9fce546b..a7529031c29f 100644 --- a/python/ql/lib/semmle/python/frameworks/data/internal/empty.model.yml +++ b/python/ql/lib/semmle/python/frameworks/data/internal/empty.model.yml @@ -11,6 +11,16 @@ extensions: extensible: sinkModel data: [] + - addsTo: + pack: codeql/python-all + extensible: barrierModel + data: [] + + - addsTo: + pack: codeql/python-all + extensible: barrierGuardModel + data: [] + - addsTo: pack: codeql/python-all extensible: summaryModel diff --git a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll index f5810944f8d9..2d92f6cb880f 100644 --- a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll @@ -7,6 +7,7 @@ private import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts +private import semmle.python.ApiGraphs private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.dataflow.new.BarrierGuards private import semmle.python.frameworks.data.ModelsAsData @@ -156,4 +157,23 @@ module UrlRedirect { /** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */ deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard; + + private predicate urlCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { + exists(API::CallNode call, API::Node parameter | + parameter = call.getAParameter() and + parameter = ModelOutput::getABarrierGuardNode("url-redirection", branch) + | + g = call.asCfgNode() and + node = parameter.asSink().asCfgNode() + ) + } + + class SanitizerFromModel extends Sanitizer { + SanitizerFromModel() { this = DataFlow::BarrierGuard::getABarrierNode() } + + override predicate sanitizes(FlowState state) { + // sanitize all flow states + any() + } + } } diff --git a/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/UrlRedirect.expected b/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/UrlRedirect.expected index d7c891b46349..551299a64dc4 100644 --- a/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/UrlRedirect.expected +++ b/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/UrlRedirect.expected @@ -52,7 +52,6 @@ edges | test.py:81:17:81:46 | ControlFlowNode for Attribute() | test.py:81:5:81:13 | ControlFlowNode for untrusted | provenance | | | test.py:82:5:82:10 | ControlFlowNode for unsafe | test.py:83:21:83:26 | ControlFlowNode for unsafe | provenance | | | test.py:90:5:90:13 | ControlFlowNode for untrusted | test.py:93:18:93:26 | ControlFlowNode for untrusted | provenance | | -| test.py:90:5:90:13 | ControlFlowNode for untrusted | test.py:95:25:95:33 | ControlFlowNode for untrusted | provenance | | | test.py:90:17:90:23 | ControlFlowNode for request | test.py:90:17:90:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | test.py:90:17:90:28 | ControlFlowNode for Attribute | test.py:90:17:90:46 | ControlFlowNode for Attribute() | provenance | dict.get | | test.py:90:17:90:46 | ControlFlowNode for Attribute() | test.py:90:5:90:13 | ControlFlowNode for untrusted | provenance | | @@ -123,7 +122,6 @@ nodes | test.py:90:17:90:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | | test.py:90:17:90:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | test.py:93:18:93:26 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | -| test.py:95:25:95:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | | test.py:111:5:111:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | | test.py:111:17:111:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | | test.py:111:17:111:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | @@ -150,7 +148,6 @@ subpaths | test.py:76:21:76:26 | ControlFlowNode for unsafe | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:76:21:76:26 | ControlFlowNode for unsafe | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:83:21:83:26 | ControlFlowNode for unsafe | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:83:21:83:26 | ControlFlowNode for unsafe | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:93:18:93:26 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:93:18:93:26 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | -| test.py:95:25:95:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:95:25:95:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:114:25:114:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:114:25:114:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:140:25:140:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:140:25:140:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:148:25:148:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:148:25:148:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | From df60819c72c5f1b4657fbe8abe0cd600abcecaec Mon Sep 17 00:00:00 2001 From: yoff Date: Thu, 11 Dec 2025 11:16:59 +0100 Subject: [PATCH 3/8] python: add qldoc --- .../python/security/dataflow/UrlRedirectCustomizations.qll | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll index 2d92f6cb880f..b3c1d3e5c363 100644 --- a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll @@ -96,6 +96,9 @@ module UrlRedirect { } } + /** + * A sink for URL redirection defined via models-as-data. + */ private class SinkFromModel extends Sink { SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() } } From 415c131ea39b6ab7a407566e016e0d31a7442ce4 Mon Sep 17 00:00:00 2001 From: yoff Date: Thu, 11 Dec 2025 11:17:18 +0100 Subject: [PATCH 4/8] all: sync API graph files --- .../data/internal/ApiGraphModels.qll | 66 ++++++++++++++++++- .../internal/ApiGraphModelsExtensions.qll | 20 ++++++ .../data/internal/ApiGraphModels.qll | 66 ++++++++++++++++++- .../internal/ApiGraphModelsExtensions.qll | 20 ++++++ 4 files changed, 168 insertions(+), 4 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll index 80ec45a3cf17..ee2cd8f57bfe 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModels.qll @@ -344,6 +344,26 @@ private predicate sinkModel(string type, string path, string kind, string model) ) } +/** Holds if a barrier model exists for the given parameters. */ +private predicate barrierModel(string type, string path, string kind, string model) { + // No deprecation adapter for barrier models, they were not around back then. + exists(QlBuiltins::ExtensionId madId | + Extensions::barrierModel(type, path, kind, madId) and + model = "MaD:" + madId.toString() + ) +} + +/** Holds if a barrier guard model exists for the given parameters. */ +private predicate barrierGuardModel( + string type, string path, string branch, string kind, string model +) { + // No deprecation adapter for barrier models, they were not around back then. + exists(QlBuiltins::ExtensionId madId | + Extensions::barrierGuardModel(type, path, branch, kind, madId) and + model = "MaD:" + madId.toString() + ) +} + /** Holds if a summary model `row` exists for the given parameters. */ private predicate summaryModel( string type, string path, string input, string output, string kind, string model @@ -400,6 +420,8 @@ predicate isRelevantType(string type) { ( sourceModel(type, _, _, _) or sinkModel(type, _, _, _) or + barrierModel(type, _, _, _) or + barrierGuardModel(type, _, _, _, _) or summaryModel(type, _, _, _, _, _) or typeModel(_, type, _) ) and @@ -427,6 +449,8 @@ predicate isRelevantFullPath(string type, string path) { ( sourceModel(type, path, _, _) or sinkModel(type, path, _, _) or + barrierModel(type, path, _, _) or + barrierGuardModel(type, path, _, _, _) or summaryModel(type, path, _, _, _, _) or typeModel(_, type, path) ) @@ -745,6 +769,32 @@ module ModelOutput { ) } + /** + * Holds if a barrier model contributed `barrier` with the given `kind`. + */ + cached + API::Node getABarrierNode(string kind, string model) { + exists(string type, string path | + barrierModel(type, path, kind, model) and + result = getNodeFromPath(type, path) + ) + } + + /** + * Holds if a barrier model contributed `barrier` with the given `kind`. + */ + cached + API::Node getABarrierGuardNode(string kind, boolean branch, string model) { + exists(string type, string path, string branch_str | + branch = true and branch_str = "true" + or + branch = false and branch_str = "false" + | + barrierGuardModel(type, path, branch_str, kind, model) and + result = getNodeFromPath(type, path) + ) + } + /** * Holds if a relevant summary exists for these parameters. */ @@ -787,15 +837,27 @@ module ModelOutput { private import codeql.mad.ModelValidation as SharedModelVal /** - * Holds if a CSV source model contributed `source` with the given `kind`. + * Holds if an external model contributed `source` with the given `kind`. */ API::Node getASourceNode(string kind) { result = getASourceNode(kind, _) } /** - * Holds if a CSV sink model contributed `sink` with the given `kind`. + * Holds if an external model contributed `sink` with the given `kind`. */ API::Node getASinkNode(string kind) { result = getASinkNode(kind, _) } + /** + * Holds if an external model contributed `barrier` with the given `kind`. + */ + API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) } + + /** + * Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`. + */ + API::Node getABarrierGuardNode(string kind, boolean branch) { + result = getABarrierGuardNode(kind, branch, _) + } + private module KindValConfig implements SharedModelVal::KindValidationConfigSig { predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsExtensions.qll b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsExtensions.qll index 3f38c498f324..2a644aabb95d 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsExtensions.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/ApiGraphModelsExtensions.qll @@ -20,6 +20,26 @@ extensible predicate sourceModel( */ extensible predicate sinkModel(string type, string path, string kind, QlBuiltins::ExtensionId madId); +/** + * Holds if the value at `(type, path)` should be seen as a barrier + * of the given `kind` and `madId` is the data extension row number. + */ +extensible predicate barrierModel( + string type, string path, string kind, QlBuiltins::ExtensionId madId +); + +/** + * Holds if the value at `(type, path)` should be seen as a barrier guard + * of the given `kind` and `madId` is the data extension row number. + * `path` is assumed to lead to a parameter of a call (possibly `self`), and + * the call is guarding the parameter. + * `branch` is either `true` or `false`, indicating which branch of the guard + * is protecting the parameter. + */ +extensible predicate barrierGuardModel( + string type, string path, string branch, string kind, QlBuiltins::ExtensionId madId +); + /** * Holds if in calls to `(type, path)`, the value referred to by `input` * can flow to the value referred to by `output` and `madId` is the data diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll index 80ec45a3cf17..ee2cd8f57bfe 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModels.qll @@ -344,6 +344,26 @@ private predicate sinkModel(string type, string path, string kind, string model) ) } +/** Holds if a barrier model exists for the given parameters. */ +private predicate barrierModel(string type, string path, string kind, string model) { + // No deprecation adapter for barrier models, they were not around back then. + exists(QlBuiltins::ExtensionId madId | + Extensions::barrierModel(type, path, kind, madId) and + model = "MaD:" + madId.toString() + ) +} + +/** Holds if a barrier guard model exists for the given parameters. */ +private predicate barrierGuardModel( + string type, string path, string branch, string kind, string model +) { + // No deprecation adapter for barrier models, they were not around back then. + exists(QlBuiltins::ExtensionId madId | + Extensions::barrierGuardModel(type, path, branch, kind, madId) and + model = "MaD:" + madId.toString() + ) +} + /** Holds if a summary model `row` exists for the given parameters. */ private predicate summaryModel( string type, string path, string input, string output, string kind, string model @@ -400,6 +420,8 @@ predicate isRelevantType(string type) { ( sourceModel(type, _, _, _) or sinkModel(type, _, _, _) or + barrierModel(type, _, _, _) or + barrierGuardModel(type, _, _, _, _) or summaryModel(type, _, _, _, _, _) or typeModel(_, type, _) ) and @@ -427,6 +449,8 @@ predicate isRelevantFullPath(string type, string path) { ( sourceModel(type, path, _, _) or sinkModel(type, path, _, _) or + barrierModel(type, path, _, _) or + barrierGuardModel(type, path, _, _, _) or summaryModel(type, path, _, _, _, _) or typeModel(_, type, path) ) @@ -745,6 +769,32 @@ module ModelOutput { ) } + /** + * Holds if a barrier model contributed `barrier` with the given `kind`. + */ + cached + API::Node getABarrierNode(string kind, string model) { + exists(string type, string path | + barrierModel(type, path, kind, model) and + result = getNodeFromPath(type, path) + ) + } + + /** + * Holds if a barrier model contributed `barrier` with the given `kind`. + */ + cached + API::Node getABarrierGuardNode(string kind, boolean branch, string model) { + exists(string type, string path, string branch_str | + branch = true and branch_str = "true" + or + branch = false and branch_str = "false" + | + barrierGuardModel(type, path, branch_str, kind, model) and + result = getNodeFromPath(type, path) + ) + } + /** * Holds if a relevant summary exists for these parameters. */ @@ -787,15 +837,27 @@ module ModelOutput { private import codeql.mad.ModelValidation as SharedModelVal /** - * Holds if a CSV source model contributed `source` with the given `kind`. + * Holds if an external model contributed `source` with the given `kind`. */ API::Node getASourceNode(string kind) { result = getASourceNode(kind, _) } /** - * Holds if a CSV sink model contributed `sink` with the given `kind`. + * Holds if an external model contributed `sink` with the given `kind`. */ API::Node getASinkNode(string kind) { result = getASinkNode(kind, _) } + /** + * Holds if an external model contributed `barrier` with the given `kind`. + */ + API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) } + + /** + * Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`. + */ + API::Node getABarrierGuardNode(string kind, boolean branch) { + result = getABarrierGuardNode(kind, branch, _) + } + private module KindValConfig implements SharedModelVal::KindValidationConfigSig { predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) } diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsExtensions.qll b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsExtensions.qll index 3f38c498f324..2a644aabb95d 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsExtensions.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/ApiGraphModelsExtensions.qll @@ -20,6 +20,26 @@ extensible predicate sourceModel( */ extensible predicate sinkModel(string type, string path, string kind, QlBuiltins::ExtensionId madId); +/** + * Holds if the value at `(type, path)` should be seen as a barrier + * of the given `kind` and `madId` is the data extension row number. + */ +extensible predicate barrierModel( + string type, string path, string kind, QlBuiltins::ExtensionId madId +); + +/** + * Holds if the value at `(type, path)` should be seen as a barrier guard + * of the given `kind` and `madId` is the data extension row number. + * `path` is assumed to lead to a parameter of a call (possibly `self`), and + * the call is guarding the parameter. + * `branch` is either `true` or `false`, indicating which branch of the guard + * is protecting the parameter. + */ +extensible predicate barrierGuardModel( + string type, string path, string branch, string kind, QlBuiltins::ExtensionId madId +); + /** * Holds if in calls to `(type, path)`, the value referred to by `input` * can flow to the value referred to by `output` and `madId` is the data From bfc42ae5f8000cad2abb2926fab6f4e63b28d495 Mon Sep 17 00:00:00 2001 From: yoff Date: Thu, 11 Dec 2025 11:21:09 +0100 Subject: [PATCH 5/8] js/ruby: add empty models --- .../frameworks/data/internal/empty.model.yml | 10 ++++++++++ .../ruby/frameworks/data/internal/empty.model.yml | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/empty.model.yml b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/empty.model.yml index 12f83f71e55b..6542a1194cab 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/data/internal/empty.model.yml +++ b/javascript/ql/lib/semmle/javascript/frameworks/data/internal/empty.model.yml @@ -16,6 +16,16 @@ extensions: extensible: summaryModel data: [] + - addsTo: + pack: codeql/javascript-all + extensible: barrierModel + data: [] + + - addsTo: + pack: codeql/javascript-all + extensible: barrierGuardModel + data: [] + - addsTo: pack: codeql/javascript-all extensible: neutralModel diff --git a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/empty.model.yml b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/empty.model.yml index b887eed7c1c5..ec68e8fcb380 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/data/internal/empty.model.yml +++ b/ruby/ql/lib/codeql/ruby/frameworks/data/internal/empty.model.yml @@ -16,6 +16,16 @@ extensions: extensible: summaryModel data: [] + - addsTo: + pack: codeql/ruby-all + extensible: barrierModel + data: [] + + - addsTo: + pack: codeql/ruby-all + extensible: barrierGuardModel + data: [] + - addsTo: pack: codeql/ruby-all extensible: neutralModel From 96b807cab9b99d65076686a571c89f745a4fec0c Mon Sep 17 00:00:00 2001 From: yoff Date: Thu, 11 Dec 2025 14:19:32 +0100 Subject: [PATCH 6/8] python: centralize external barrier guard definition --- .../dataflow/new/internal/DataFlowPublic.qll | 39 ++++++++++++++++++- .../dataflow/UrlRedirectCustomizations.qll | 14 ++----- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll index ffecbcba57ac..0c3e56f02bff 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll @@ -600,15 +600,52 @@ signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean br module BarrierGuard { /** Gets a node that is safely guarded by the given guard check. */ ExprNode getABarrierNode() { + result = ParameterizedBarrierGuard::getABarrierNode(_) + } + + private predicate extendedGuardChecks(GuardNode g, ControlFlowNode node, boolean branch, Unit u) { + guardChecks(g, node, branch) + } +} + +bindingset[this] +private signature class ParamSig; + +private module WithParam { + signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean branch, P param); +} + +module ParameterizedBarrierGuard::guardChecksSig/4 guardChecks> { + /** Gets a node that is safely guarded by the given guard check with parameter `param`. */ + ExprNode getABarrierNode(P param) { exists(GuardNode g, EssaDefinition def, ControlFlowNode node, boolean branch | AdjacentUses::useOfDef(def, node) and - guardChecks(g, node, branch) and + guardChecks(g, node, branch, param) and AdjacentUses::useOfDef(def, result.asCfgNode()) and g.controlsBlock(result.asCfgNode().getBasicBlock(), branch) ) } } +module ExternalBarrierGuard { + private import semmle.python.ApiGraphs + + predicate guardCheck(GuardNode g, ControlFlowNode node, boolean branch, string kind) { + exists(API::CallNode call, API::Node parameter | + parameter = call.getAParameter() and + parameter = ModelOutput::getABarrierGuardNode(kind, branch) + | + g = call.asCfgNode() and + node = parameter.asSink().asCfgNode() + ) + } + + /** Gets a node that is an external barrier of the given kind. */ + ExprNode getAnExternalBarrierNode(string kind) { + result = ParameterizedBarrierGuard::getABarrierNode(kind) + } +} + /** * Algebraic datatype for tracking data content associated with values. * Content can be collection elements or object attributes. diff --git a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll index b3c1d3e5c363..a1c652192f41 100644 --- a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll @@ -161,18 +161,10 @@ module UrlRedirect { /** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */ deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard; - private predicate urlCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { - exists(API::CallNode call, API::Node parameter | - parameter = call.getAParameter() and - parameter = ModelOutput::getABarrierGuardNode("url-redirection", branch) - | - g = call.asCfgNode() and - node = parameter.asSink().asCfgNode() - ) - } - class SanitizerFromModel extends Sanitizer { - SanitizerFromModel() { this = DataFlow::BarrierGuard::getABarrierNode() } + SanitizerFromModel() { + this = DataFlow::ExternalBarrierGuard::getAnExternalBarrierNode("url-redirection") + } override predicate sanitizes(FlowState state) { // sanitize all flow states From cdd26470cf9bab8d944b2b6dbe4182a458421454 Mon Sep 17 00:00:00 2001 From: yoff Date: Thu, 11 Dec 2025 15:03:15 +0100 Subject: [PATCH 7/8] python: add qldoc --- .../dataflow/new/internal/DataFlowPublic.qll | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll index 0c3e56f02bff..65e03963379a 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPublic.qll @@ -615,6 +615,12 @@ private module WithParam { signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean branch, P param); } +/** + * Provides a set of barrier nodes for a guard that validates a node. + * + * This is expected to be used in `isBarrier`/`isSanitizer` definitions + * in data flow and taint tracking. + */ module ParameterizedBarrierGuard::guardChecksSig/4 guardChecks> { /** Gets a node that is safely guarded by the given guard check with parameter `param`. */ ExprNode getABarrierNode(P param) { @@ -627,10 +633,16 @@ module ParameterizedBarrierGuard::guardChecksSig/4 guar } } +/** + * Provides a set of barrier nodes for a guard that validates a node as described by an external predicate. + * + * This is expected to be used in `isBarrier`/`isSanitizer` definitions + * in data flow and taint tracking. + */ module ExternalBarrierGuard { private import semmle.python.ApiGraphs - predicate guardCheck(GuardNode g, ControlFlowNode node, boolean branch, string kind) { + private predicate guardCheck(GuardNode g, ControlFlowNode node, boolean branch, string kind) { exists(API::CallNode call, API::Node parameter | parameter = call.getAParameter() and parameter = ModelOutput::getABarrierGuardNode(kind, branch) From 2e78b1494718828e3ffb749d60a1303312370e27 Mon Sep 17 00:00:00 2001 From: yoff Date: Thu, 11 Dec 2025 15:03:49 +0100 Subject: [PATCH 8/8] python: MaD expectations --- .../CWE-022-UnsafeUnpacking/UnsafeUnpack.expected | 2 +- .../Security/CWE-409/DecompressionBombs.expected | 10 +++++----- .../SqlInjection.expected | 2 +- .../Tests1/HeaderInjection.expected | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected index 0f334d45ea83..69bb8d30e8f4 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected +++ b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected @@ -75,7 +75,7 @@ edges | UnsafeUnpack.py:161:19:161:21 | ControlFlowNode for tar | UnsafeUnpack.py:163:33:163:35 | ControlFlowNode for tar | provenance | | | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | UnsafeUnpack.py:161:19:161:21 | ControlFlowNode for tar | provenance | | | UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | Config | -| UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | MaD:69 | +| UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | MaD:70 | | UnsafeUnpack.py:163:23:163:28 | ControlFlowNode for member | UnsafeUnpack.py:166:37:166:42 | ControlFlowNode for member | provenance | | | UnsafeUnpack.py:163:33:163:35 | ControlFlowNode for tar | UnsafeUnpack.py:163:23:163:28 | ControlFlowNode for member | provenance | | | UnsafeUnpack.py:166:23:166:28 | [post] ControlFlowNode for result | UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | provenance | | diff --git a/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.expected b/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.expected index 17c28aa1d95d..e32edeb702bb 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.expected +++ b/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.expected @@ -1,23 +1,23 @@ edges | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:11:21:11:29 | ControlFlowNode for file_path | provenance | | | test.py:11:5:11:35 | ControlFlowNode for Attribute() | test.py:11:5:11:52 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:35 | ControlFlowNode for Attribute() | provenance | MaD:86 | +| test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:35 | ControlFlowNode for Attribute() | provenance | MaD:87 | | test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:52 | ControlFlowNode for Attribute() | provenance | Config | | test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:12:21:12:29 | ControlFlowNode for file_path | provenance | | | test.py:12:5:12:35 | ControlFlowNode for Attribute() | test.py:12:5:12:48 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:35 | ControlFlowNode for Attribute() | provenance | MaD:86 | +| test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:35 | ControlFlowNode for Attribute() | provenance | MaD:87 | | test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:48 | ControlFlowNode for Attribute() | provenance | Config | | test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:14:26:14:34 | ControlFlowNode for file_path | provenance | | | test.py:14:10:14:35 | ControlFlowNode for Attribute() | test.py:15:14:15:29 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:14:10:14:35 | ControlFlowNode for Attribute() | provenance | MaD:86 | +| test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:14:10:14:35 | ControlFlowNode for Attribute() | provenance | MaD:87 | | test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:15:14:15:29 | ControlFlowNode for Attribute() | provenance | Config | | test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:18:26:18:34 | ControlFlowNode for file_path | provenance | | | test.py:18:10:18:35 | ControlFlowNode for Attribute() | test.py:19:14:19:39 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:18:10:18:35 | ControlFlowNode for Attribute() | provenance | MaD:86 | +| test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:18:10:18:35 | ControlFlowNode for Attribute() | provenance | MaD:87 | | test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:19:14:19:39 | ControlFlowNode for Attribute() | provenance | Config | | test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:22:21:22:29 | ControlFlowNode for file_path | provenance | | | test.py:22:5:22:30 | ControlFlowNode for Attribute() | test.py:22:5:22:60 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:30 | ControlFlowNode for Attribute() | provenance | MaD:86 | +| test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:30 | ControlFlowNode for Attribute() | provenance | MaD:87 | | test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:60 | ControlFlowNode for Attribute() | provenance | Config | | test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:24:18:24:26 | ControlFlowNode for file_path | provenance | | | test.py:24:18:24:26 | ControlFlowNode for file_path | test.py:24:5:24:52 | ControlFlowNode for Attribute() | provenance | Config | diff --git a/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.expected b/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.expected index 1e4ba8b95305..d59e639d641b 100644 --- a/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.expected +++ b/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.expected @@ -1,5 +1,5 @@ edges -| test.py:6:14:6:21 | ControlFlowNode for Attribute | test.py:6:14:6:24 | ControlFlowNode for Subscript | provenance | Src:MaD:17 | +| test.py:6:14:6:21 | ControlFlowNode for Attribute | test.py:6:14:6:24 | ControlFlowNode for Subscript | provenance | Src:MaD:18 | nodes | test.py:6:14:6:21 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | | test.py:6:14:6:24 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | diff --git a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.expected b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.expected index cea505fe39db..6c5f8363c487 100644 --- a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.expected +++ b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.expected @@ -14,10 +14,10 @@ edges | http_test.py:5:16:5:19 | ControlFlowNode for self | http_test.py:6:45:6:53 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | http_test.py:6:9:6:19 | ControlFlowNode for parsed_path | http_test.py:7:40:7:56 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | http_test.py:6:23:6:54 | ControlFlowNode for Attribute() | http_test.py:6:9:6:19 | ControlFlowNode for parsed_path | provenance | | -| http_test.py:6:45:6:53 | ControlFlowNode for Attribute | http_test.py:6:23:6:54 | ControlFlowNode for Attribute() | provenance | MaD:77 | +| http_test.py:6:45:6:53 | ControlFlowNode for Attribute | http_test.py:6:23:6:54 | ControlFlowNode for Attribute() | provenance | MaD:78 | | http_test.py:7:9:7:14 | ControlFlowNode for params | http_test.py:8:23:8:28 | ControlFlowNode for params | provenance | | | http_test.py:7:18:7:57 | ControlFlowNode for Attribute() | http_test.py:7:9:7:14 | ControlFlowNode for params | provenance | | -| http_test.py:7:40:7:56 | ControlFlowNode for Attribute | http_test.py:7:18:7:57 | ControlFlowNode for Attribute() | provenance | MaD:76 | +| http_test.py:7:40:7:56 | ControlFlowNode for Attribute | http_test.py:7:18:7:57 | ControlFlowNode for Attribute() | provenance | MaD:77 | | http_test.py:8:9:8:19 | ControlFlowNode for input_value | http_test.py:12:40:12:50 | ControlFlowNode for input_value | provenance | | | http_test.py:8:23:8:28 | ControlFlowNode for params | http_test.py:8:23:8:47 | ControlFlowNode for Attribute() | provenance | dict.get | | http_test.py:8:23:8:47 | ControlFlowNode for Attribute() | http_test.py:8:9:8:19 | ControlFlowNode for input_value | provenance | |