From 5a77128a8bcf9b090d76b6dc09cf53f0ecd20815 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 23 Mar 2026 11:27:05 +0000
Subject: [PATCH 01/33] C++: Disable cpp/implicit-function-declaration on BMN
databases.
---
.../Underspecified Functions/ImplicitFunctionDeclaration.ql | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
index 6a55557cf70..007ef71a163 100644
--- a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
+++ b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
@@ -17,6 +17,11 @@ import TooFewArguments
import TooManyArguments
import semmle.code.cpp.commons.Exclusions
+/*
+ * This query is not compatible with build mode: none databases, and has
+ * no results on those databases.
+ */
+
predicate locInfo(Locatable e, File file, int line, int col) {
e.getFile() = file and
e.getLocation().getStartLine() = line and
@@ -39,6 +44,7 @@ predicate isCompiledAsC(File f) {
from FunctionDeclarationEntry fdeIm, FunctionCall fc
where
isCompiledAsC(fdeIm.getFile()) and
+ not any(Compilation c).buildModeNone() and
not isFromMacroDefinition(fc) and
fdeIm.isImplicit() and
sameLocation(fdeIm, fc) and
From 39056e44771373f23bcd8a561bbf60a6c0122e60 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 23 Mar 2026 12:28:12 +0000
Subject: [PATCH 02/33] C++: Change note.
---
.../change-notes/2026-03-23-implicit-function-declaration.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 cpp/ql/src/change-notes/2026-03-23-implicit-function-declaration.md
diff --git a/cpp/ql/src/change-notes/2026-03-23-implicit-function-declaration.md b/cpp/ql/src/change-notes/2026-03-23-implicit-function-declaration.md
new file mode 100644
index 00000000000..8c2c431ec24
--- /dev/null
+++ b/cpp/ql/src/change-notes/2026-03-23-implicit-function-declaration.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* The "Implicit function declaration" (`cpp/implicit-function-declaration`) query no longer produces results on `build mode: none` databases. These results were found to be very noisy and fundamentally imprecise in this mode.
From 60f9ce4ce7485269027caefacd2fa5ee73099d90 Mon Sep 17 00:00:00 2001
From: Taus
Date: Fri, 20 Mar 2026 13:55:58 +0000
Subject: [PATCH 03/33] Python: Port UnreachableCode.ql
---
python/ql/src/Statements/UnreachableCode.ql | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/python/ql/src/Statements/UnreachableCode.ql b/python/ql/src/Statements/UnreachableCode.ql
index 55582ed2f06..200e073cff6 100644
--- a/python/ql/src/Statements/UnreachableCode.ql
+++ b/python/ql/src/Statements/UnreachableCode.ql
@@ -13,7 +13,7 @@
*/
import python
-private import LegacyPointsTo
+private import semmle.python.ApiGraphs
predicate typing_import(ImportingStmt is) {
exists(Module m |
@@ -34,11 +34,7 @@ predicate unique_yield(Stmt s) {
/** Holds if `contextlib.suppress` may be used in the same scope as `s` */
predicate suppression_in_scope(Stmt s) {
exists(With w |
- w.getContextExpr()
- .(Call)
- .getFunc()
- .(ExprWithPointsTo)
- .pointsTo(Value::named("contextlib.suppress")) and
+ w.getContextExpr() = API::moduleImport("contextlib").getMember("suppress").getACall().asExpr() and
w.getScope() = s.getScope()
)
}
From 0ea80ac184013820dbd2da2e5b4ba24c8ea92f3d Mon Sep 17 00:00:00 2001
From: Taus
Date: Fri, 20 Feb 2026 15:03:15 +0000
Subject: [PATCH 04/33] Python: Port UnusedExceptionObject.ql
Depending on whether other queries depend on this, we may end up moving
the exception utility functions to a more central location.
---
.../src/Statements/UnusedExceptionObject.ql | 46 +++++++++++++++++--
1 file changed, 42 insertions(+), 4 deletions(-)
diff --git a/python/ql/src/Statements/UnusedExceptionObject.ql b/python/ql/src/Statements/UnusedExceptionObject.ql
index 9a6a3650b7e..890cdc963ac 100644
--- a/python/ql/src/Statements/UnusedExceptionObject.ql
+++ b/python/ql/src/Statements/UnusedExceptionObject.ql
@@ -12,11 +12,49 @@
*/
import python
-private import LegacyPointsTo
+private import semmle.python.dataflow.new.internal.DataFlowDispatch
+private import semmle.python.dataflow.new.internal.Builtins
+private import semmle.python.ApiGraphs
-from Call call, ClassValue ex
+/**
+ * Holds if `cls` is a user-defined exception class, i.e. it transitively
+ * extends one of the builtin exception base classes.
+ */
+predicate isUserDefinedExceptionClass(Class cls) {
+ cls.getABase() =
+ API::builtin(["BaseException", "Exception"]).getAValueReachableFromSource().asExpr()
+ or
+ isUserDefinedExceptionClass(getADirectSuperclass(cls))
+}
+
+/**
+ * Gets the name of a builtin exception class.
+ */
+string getBuiltinExceptionName() {
+ result = Builtins::getBuiltinName() and
+ (
+ result.matches("%Error") or
+ result.matches("%Exception") or
+ result.matches("%Warning") or
+ result =
+ ["GeneratorExit", "KeyboardInterrupt", "StopIteration", "StopAsyncIteration", "SystemExit"]
+ )
+}
+
+/**
+ * Holds if `call` is an instantiation of an exception class.
+ */
+predicate isExceptionInstantiation(Call call) {
+ exists(Class cls |
+ classTracker(cls).asExpr() = call.getFunc() and
+ isUserDefinedExceptionClass(cls)
+ )
+ or
+ call.getFunc() = API::builtin(getBuiltinExceptionName()).getAValueReachableFromSource().asExpr()
+}
+
+from Call call
where
- call.getFunc().(ExprWithPointsTo).pointsTo(ex) and
- ex.getASuperType() = ClassValue::exception() and
+ isExceptionInstantiation(call) and
exists(ExprStmt s | s.getValue() = call)
select call, "Instantiating an exception, but not raising it, has no effect."
From bb9873dc8fff136f5e119400ca9cc67f08d0cdb2 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 27 Mar 2026 16:40:45 +0000
Subject: [PATCH 05/33] C++: Increase the query precision to high.
---
.../Underspecified Functions/ImplicitFunctionDeclaration.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
index 007ef71a163..0cf6c8b3714 100644
--- a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
+++ b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
@@ -5,7 +5,7 @@
* may lead to unpredictable behavior.
* @kind problem
* @problem.severity warning
- * @precision medium
+ * @precision high
* @id cpp/implicit-function-declaration
* @tags correctness
* maintainability
From 50681a3c42fb901cd231e0988c1f3047acbff075 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 27 Mar 2026 16:47:31 +0000
Subject: [PATCH 06/33] C++: Add note to the .qhelp.
---
.../ImplicitFunctionDeclaration.qhelp | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.qhelp b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.qhelp
index 6ff60d38341..d9b5a022077 100644
--- a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.qhelp
+++ b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.qhelp
@@ -14,6 +14,9 @@ function may behave unpredictably.
This may indicate a misspelled function name, or that the required header containing
the function declaration has not been included.
+Note: This query is not compatible with build mode: none databases, and produces
+no results on those databases.
+
Provide an explicit declaration of the function before invoking it.
@@ -26,4 +29,4 @@ the function declaration has not been included.
SEI CERT C Coding Standard: DCL31-C. Declare identifiers before using them
-
\ No newline at end of file
+
From 4f3108c444156d5a072c19845f7083690a0342b0 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 27 Mar 2026 17:04:05 +0000
Subject: [PATCH 07/33] C++: Update change note.
---
.../src/change-notes/2026-03-23-implicit-function-declaration.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/cpp/ql/src/change-notes/2026-03-23-implicit-function-declaration.md b/cpp/ql/src/change-notes/2026-03-23-implicit-function-declaration.md
index 8c2c431ec24..4fc4808f40c 100644
--- a/cpp/ql/src/change-notes/2026-03-23-implicit-function-declaration.md
+++ b/cpp/ql/src/change-notes/2026-03-23-implicit-function-declaration.md
@@ -1,4 +1,5 @@
---
category: minorAnalysis
---
+* The "Implicit function declaration" (`cpp/implicit-function-declaration`) query has been promoted to `@precision high`.
* The "Implicit function declaration" (`cpp/implicit-function-declaration`) query no longer produces results on `build mode: none` databases. These results were found to be very noisy and fundamentally imprecise in this mode.
From a9cce1c0fa75b167c549b46ab350d7970929750a Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 27 Mar 2026 17:32:03 +0000
Subject: [PATCH 08/33] C++: Undo increasing query precision.
---
.../Underspecified Functions/ImplicitFunctionDeclaration.ql | 2 +-
.../change-notes/2026-03-23-implicit-function-declaration.md | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
index 0cf6c8b3714..007ef71a163 100644
--- a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
+++ b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
@@ -5,7 +5,7 @@
* may lead to unpredictable behavior.
* @kind problem
* @problem.severity warning
- * @precision high
+ * @precision medium
* @id cpp/implicit-function-declaration
* @tags correctness
* maintainability
diff --git a/cpp/ql/src/change-notes/2026-03-23-implicit-function-declaration.md b/cpp/ql/src/change-notes/2026-03-23-implicit-function-declaration.md
index 4fc4808f40c..8c2c431ec24 100644
--- a/cpp/ql/src/change-notes/2026-03-23-implicit-function-declaration.md
+++ b/cpp/ql/src/change-notes/2026-03-23-implicit-function-declaration.md
@@ -1,5 +1,4 @@
---
category: minorAnalysis
---
-* The "Implicit function declaration" (`cpp/implicit-function-declaration`) query has been promoted to `@precision high`.
* The "Implicit function declaration" (`cpp/implicit-function-declaration`) query no longer produces results on `build mode: none` databases. These results were found to be very noisy and fundamentally imprecise in this mode.
From c5ef1f6342bbc04bf34f95abc397df1089617d2c Mon Sep 17 00:00:00 2001
From: Taus
Date: Fri, 20 Mar 2026 13:56:07 +0000
Subject: [PATCH 09/33] Python: Port UseOfExit.ql
---
python/ql/src/Statements/UseOfExit.ql | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/Statements/UseOfExit.ql b/python/ql/src/Statements/UseOfExit.ql
index 437ff93b537..2310a839f67 100644
--- a/python/ql/src/Statements/UseOfExit.ql
+++ b/python/ql/src/Statements/UseOfExit.ql
@@ -12,10 +12,12 @@
*/
import python
-private import LegacyPointsTo
+private import semmle.python.ApiGraphs
from CallNode call, string name
-where call.getFunction().(ControlFlowNodeWithPointsTo).pointsTo(Value::siteQuitter(name))
+where
+ name = ["exit", "quit"] and
+ call = API::builtin(name).getACall().asCfgNode()
select call,
"The '" + name +
"' site.Quitter object may not exist if the 'site' module is not loaded or is modified."
From 84c01bc255b549f9b34fece46a588c3e1205f6d6 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 2 Apr 2026 11:26:39 +0100
Subject: [PATCH 10/33] C++: Upgrade query precision.
---
cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql b/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql
index 7f0a4833cb5..5842b9474f7 100644
--- a/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql
+++ b/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql
@@ -5,7 +5,7 @@
* @kind problem
* @problem.severity error
* @security-severity 7.5
- * @precision medium
+ * @precision high
* @id cpp/wrong-type-format-argument
* @tags reliability
* correctness
From fca567f6ea98abe390b446701fa82ff5c386b7d9 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 2 Apr 2026 11:26:21 +0100
Subject: [PATCH 11/33] C++: Change note.
---
.../src/change-notes/2026-04-02-wrong-type-format-argument.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 cpp/ql/src/change-notes/2026-04-02-wrong-type-format-argument.md
diff --git a/cpp/ql/src/change-notes/2026-04-02-wrong-type-format-argument.md b/cpp/ql/src/change-notes/2026-04-02-wrong-type-format-argument.md
new file mode 100644
index 00000000000..f8b9085dacc
--- /dev/null
+++ b/cpp/ql/src/change-notes/2026-04-02-wrong-type-format-argument.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* The "Wrong type of arguments to formatting function" (`cpp/wrong-type-format-argument`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
From b41a4ff5e4c06ac0e2308786bf82490e00b232a3 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 2 Apr 2026 11:28:19 +0100
Subject: [PATCH 12/33] C++: Upgrade query precision.
---
cpp/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql b/cpp/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql
index 6747d177c80..b05bd637dc2 100644
--- a/cpp/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql
+++ b/cpp/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql
@@ -5,7 +5,7 @@
* @kind problem
* @problem.severity warning
* @security-severity 8.1
- * @precision medium
+ * @precision high
* @id cpp/integer-multiplication-cast-to-long
* @tags reliability
* security
From 909b55a40a2e4a85ce4ea04ff8d6a179c0b6399e Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 2 Apr 2026 11:28:34 +0100
Subject: [PATCH 13/33] C++: Change note.
---
.../2026-04-02-integer-multiplication-cast-to-long.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 cpp/ql/src/change-notes/2026-04-02-integer-multiplication-cast-to-long.md
diff --git a/cpp/ql/src/change-notes/2026-04-02-integer-multiplication-cast-to-long.md b/cpp/ql/src/change-notes/2026-04-02-integer-multiplication-cast-to-long.md
new file mode 100644
index 00000000000..cd6796b408f
--- /dev/null
+++ b/cpp/ql/src/change-notes/2026-04-02-integer-multiplication-cast-to-long.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* The "Multiplication result converted to larger type" (`cpp/integer-multiplication-cast-to-long`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
From 520e95d92c255dc4e54ccaadc8c31121f0651ded Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 2 Apr 2026 11:30:34 +0100
Subject: [PATCH 14/33] C++: Upgrade query precision.
---
cpp/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql b/cpp/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql
index 3f330807304..7d9ef88adea 100644
--- a/cpp/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql
+++ b/cpp/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql
@@ -6,7 +6,7 @@
* @kind problem
* @problem.severity warning
* @security-severity 7.8
- * @precision medium
+ * @precision high
* @tags reliability
* security
* external/cwe/cwe-190
From 9dbbdef4cbab1c6bb4f63825112f329b3f5f5fad Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 2 Apr 2026 11:30:52 +0100
Subject: [PATCH 15/33] C++: Change note.
---
.../src/change-notes/2026-04-02-comparison-with-wider-type.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 cpp/ql/src/change-notes/2026-04-02-comparison-with-wider-type.md
diff --git a/cpp/ql/src/change-notes/2026-04-02-comparison-with-wider-type.md b/cpp/ql/src/change-notes/2026-04-02-comparison-with-wider-type.md
new file mode 100644
index 00000000000..c84e1dba404
--- /dev/null
+++ b/cpp/ql/src/change-notes/2026-04-02-comparison-with-wider-type.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* The "Comparison of narrow type with wide type in loop condition" (`cpp/comparison-with-wider-type`) query has been upgraded to `high` precision. This query will now run in the default code scanning suite.
From 2d02056e5c2434b85a853b8d414a5bffa327ed1c Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 2 Apr 2026 11:34:54 +0100
Subject: [PATCH 16/33] C++: Second change note.
---
.../change-notes/2026-04-02-implicit-function-declaration.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 cpp/ql/src/change-notes/2026-04-02-implicit-function-declaration.md
diff --git a/cpp/ql/src/change-notes/2026-04-02-implicit-function-declaration.md b/cpp/ql/src/change-notes/2026-04-02-implicit-function-declaration.md
new file mode 100644
index 00000000000..dd0dbd4bc7d
--- /dev/null
+++ b/cpp/ql/src/change-notes/2026-04-02-implicit-function-declaration.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* The "Implicit function declaration" (`cpp/implicit-function-declaration`) query has been upgraded to `high` precision.
From e83658ed06c43ea71f6d35adf1285592ae15148d Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 2 Apr 2026 11:38:09 +0100
Subject: [PATCH 17/33] C++: Upgrade query precision.
---
.../Underspecified Functions/ImplicitFunctionDeclaration.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
index 007ef71a163..0cf6c8b3714 100644
--- a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
+++ b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
@@ -5,7 +5,7 @@
* may lead to unpredictable behavior.
* @kind problem
* @problem.severity warning
- * @precision medium
+ * @precision high
* @id cpp/implicit-function-declaration
* @tags correctness
* maintainability
From 9eabfc5fdc5d3b809641e4f8b81c1188b6d9a743 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 2 Apr 2026 11:39:45 +0100
Subject: [PATCH 18/33] Update cpp/ql/src/Likely Bugs/Underspecified
Functions/ImplicitFunctionDeclaration.ql
Co-authored-by: Jeroen Ketema <93738568+jketema@users.noreply.github.com>
---
.../Underspecified Functions/ImplicitFunctionDeclaration.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
index 0cf6c8b3714..00b29efbd0f 100644
--- a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
+++ b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.ql
@@ -18,7 +18,7 @@ import TooManyArguments
import semmle.code.cpp.commons.Exclusions
/*
- * This query is not compatible with build mode: none databases, and has
+ * This query is not compatible with build mode: none databases, and produces
* no results on those databases.
*/
From 56af9a84ab4f6232bf03c10bc08def2a4611cb83 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 2 Apr 2026 11:40:51 +0100
Subject: [PATCH 19/33] Update cpp/ql/src/Likely Bugs/Underspecified
Functions/ImplicitFunctionDeclaration.qhelp
---
.../Underspecified Functions/ImplicitFunctionDeclaration.qhelp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.qhelp b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.qhelp
index d9b5a022077..90a98e1bf57 100644
--- a/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.qhelp
+++ b/cpp/ql/src/Likely Bugs/Underspecified Functions/ImplicitFunctionDeclaration.qhelp
@@ -14,7 +14,7 @@ function may behave unpredictably.
This may indicate a misspelled function name, or that the required header containing
the function declaration has not been included.
-Note: This query is not compatible with build mode: none databases, and produces
+
Note: This query is not compatible with build mode: none databases, and produces
no results on those databases.
From 5866bcc8816a23b612043ba4dbb847eb4f36a019 Mon Sep 17 00:00:00 2001
From: Jeroen Ketema
Date: Thu, 2 Apr 2026 15:41:41 +0200
Subject: [PATCH 20/33] Actions: Add FP test for
`actions/missing-workflow-permissions`
---
.../Security/CWE-275/.github/workflows/perms11.yml | 9 +++++++++
.../Security/CWE-275/.github/workflows/perms12.yml | 11 +++++++++++
.../CWE-275/MissingActionsPermissions.expected | 1 +
3 files changed, 21 insertions(+)
create mode 100644 actions/ql/test/query-tests/Security/CWE-275/.github/workflows/perms11.yml
create mode 100644 actions/ql/test/query-tests/Security/CWE-275/.github/workflows/perms12.yml
diff --git a/actions/ql/test/query-tests/Security/CWE-275/.github/workflows/perms11.yml b/actions/ql/test/query-tests/Security/CWE-275/.github/workflows/perms11.yml
new file mode 100644
index 00000000000..717cdabc302
--- /dev/null
+++ b/actions/ql/test/query-tests/Security/CWE-275/.github/workflows/perms11.yml
@@ -0,0 +1,9 @@
+on:
+ workflow_call:
+
+jobs:
+ build:
+ name: Build and test
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/deploy-pages
diff --git a/actions/ql/test/query-tests/Security/CWE-275/.github/workflows/perms12.yml b/actions/ql/test/query-tests/Security/CWE-275/.github/workflows/perms12.yml
new file mode 100644
index 00000000000..25ac1f53248
--- /dev/null
+++ b/actions/ql/test/query-tests/Security/CWE-275/.github/workflows/perms12.yml
@@ -0,0 +1,11 @@
+on:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ id-token: write
+ pages: write
+
+jobs:
+ call-workflow:
+ uses: ./.github/workflows/perms11.yml
diff --git a/actions/ql/test/query-tests/Security/CWE-275/MissingActionsPermissions.expected b/actions/ql/test/query-tests/Security/CWE-275/MissingActionsPermissions.expected
index 52a045e0de2..74edf8a7d38 100644
--- a/actions/ql/test/query-tests/Security/CWE-275/MissingActionsPermissions.expected
+++ b/actions/ql/test/query-tests/Security/CWE-275/MissingActionsPermissions.expected
@@ -6,3 +6,4 @@
| .github/workflows/perms8.yml:7:5:10:33 | Job: build | Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {id-token: write, pages: write} |
| .github/workflows/perms9.yml:7:5:10:44 | Job: build | Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {packages: write} |
| .github/workflows/perms10.yml:7:5:10:33 | Job: build | Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read, models: read} |
+| .github/workflows/perms11.yml:6:5:9:33 | Job: build | Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {id-token: write, pages: write} |
From 74e6d3474d4a0f03a870a0ab5f23cb66f325ad3d Mon Sep 17 00:00:00 2001
From: Jeroen Ketema
Date: Thu, 2 Apr 2026 15:42:45 +0200
Subject: [PATCH 21/33] Actions: Correctly check permissions in
`actions/missing-workflow-permissions`
---
.../CWE-275/MissingActionsPermissions.ql | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/actions/ql/src/Security/CWE-275/MissingActionsPermissions.ql b/actions/ql/src/Security/CWE-275/MissingActionsPermissions.ql
index a8bd8a5f93d..00f601fd5da 100644
--- a/actions/ql/src/Security/CWE-275/MissingActionsPermissions.ql
+++ b/actions/ql/src/Security/CWE-275/MissingActionsPermissions.ql
@@ -26,10 +26,23 @@ string permissionsForJob(Job job) {
"{" + concat(string permission | permission = jobNeedsPermission(job) | permission, ", ") + "}"
}
+predicate jobHasPermissions(Job job) {
+ exists(job.getPermissions())
+ or
+ exists(job.getEnclosingWorkflow().getPermissions())
+ or
+ // The workflow is reusable and cannot be triggered in any other way; check callers
+ exists(ReusableWorkflow r | r = job.getEnclosingWorkflow() |
+ not exists(Event e | e = r.getOn().getAnEvent() | e.getName() != "workflow_call") and
+ forall(Job caller | caller = job.getEnclosingWorkflow().(ReusableWorkflow).getACaller() |
+ jobHasPermissions(caller)
+ )
+ )
+}
+
from Job job, string permissions
where
- not exists(job.getPermissions()) and
- not exists(job.getEnclosingWorkflow().getPermissions()) and
+ not jobHasPermissions(job) and
// exists a trigger event that is not a workflow_call
exists(Event e |
e = job.getATriggerEvent() and
From 47409d1c599c2367c67e649bf89a4817cee9e1b2 Mon Sep 17 00:00:00 2001
From: Jeroen Ketema
Date: Thu, 2 Apr 2026 15:43:49 +0200
Subject: [PATCH 22/33] Actions: Update expected test results
---
.../Security/CWE-275/MissingActionsPermissions.expected | 1 -
1 file changed, 1 deletion(-)
diff --git a/actions/ql/test/query-tests/Security/CWE-275/MissingActionsPermissions.expected b/actions/ql/test/query-tests/Security/CWE-275/MissingActionsPermissions.expected
index 74edf8a7d38..52a045e0de2 100644
--- a/actions/ql/test/query-tests/Security/CWE-275/MissingActionsPermissions.expected
+++ b/actions/ql/test/query-tests/Security/CWE-275/MissingActionsPermissions.expected
@@ -6,4 +6,3 @@
| .github/workflows/perms8.yml:7:5:10:33 | Job: build | Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {id-token: write, pages: write} |
| .github/workflows/perms9.yml:7:5:10:44 | Job: build | Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {packages: write} |
| .github/workflows/perms10.yml:7:5:10:33 | Job: build | Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read, models: read} |
-| .github/workflows/perms11.yml:6:5:9:33 | Job: build | Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {id-token: write, pages: write} |
From 87f9b9581ec3bb327ea78fbc82a6b3f3c4c2c355 Mon Sep 17 00:00:00 2001
From: Jeroen Ketema
Date: Thu, 2 Apr 2026 15:48:45 +0200
Subject: [PATCH 23/33] Actions: Add change note
---
actions/ql/src/change-notes/2026-04-02-permissions.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 actions/ql/src/change-notes/2026-04-02-permissions.md
diff --git a/actions/ql/src/change-notes/2026-04-02-permissions.md b/actions/ql/src/change-notes/2026-04-02-permissions.md
new file mode 100644
index 00000000000..2672a30ef87
--- /dev/null
+++ b/actions/ql/src/change-notes/2026-04-02-permissions.md
@@ -0,0 +1,4 @@
+---
+category: minorAnalysis
+---
+* The query `actions/missing-workflow-permissions` no longer produces false positive results on reusable workflows where all callers set permissions.
\ No newline at end of file
From 3769a8a48287688b1c7469233d32d46dcd636660 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Tue, 7 Apr 2026 12:51:56 +0100
Subject: [PATCH 24/33] C++: Update code scanning suite .expected.
---
.../integration-tests/query-suite/cpp-code-scanning.qls.expected | 1 +
1 file changed, 1 insertion(+)
diff --git a/cpp/ql/integration-tests/query-suite/cpp-code-scanning.qls.expected b/cpp/ql/integration-tests/query-suite/cpp-code-scanning.qls.expected
index 57d240fd795..6875fbf43de 100644
--- a/cpp/ql/integration-tests/query-suite/cpp-code-scanning.qls.expected
+++ b/cpp/ql/integration-tests/query-suite/cpp-code-scanning.qls.expected
@@ -11,6 +11,7 @@ ql/cpp/ql/src/Likely Bugs/Arithmetic/SignedOverflowCheck.ql
ql/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql
ql/cpp/ql/src/Likely Bugs/Format/SnprintfOverflow.ql
ql/cpp/ql/src/Likely Bugs/Format/WrongNumberOfFormatArguments.ql
+ql/cpp/ql/src/Likely Bugs/Format/WrongTypeFormatArguments.ql
ql/cpp/ql/src/Likely Bugs/Memory Management/AllocaInLoop.ql
ql/cpp/ql/src/Likely Bugs/Memory Management/PointerOverflow.ql
ql/cpp/ql/src/Likely Bugs/Memory Management/ReturnStackAllocatedMemory.ql
From f2292643a35362236f1e729bf7833839fb36bb56 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Tue, 7 Apr 2026 12:53:53 +0100
Subject: [PATCH 25/33] C++: Update code scanning suite .expected.
---
.../integration-tests/query-suite/cpp-code-scanning.qls.expected | 1 +
1 file changed, 1 insertion(+)
diff --git a/cpp/ql/integration-tests/query-suite/cpp-code-scanning.qls.expected b/cpp/ql/integration-tests/query-suite/cpp-code-scanning.qls.expected
index 57d240fd795..6cc662aee3b 100644
--- a/cpp/ql/integration-tests/query-suite/cpp-code-scanning.qls.expected
+++ b/cpp/ql/integration-tests/query-suite/cpp-code-scanning.qls.expected
@@ -7,6 +7,7 @@ ql/cpp/ql/src/Diagnostics/ExtractedFiles.ql
ql/cpp/ql/src/Diagnostics/ExtractionWarnings.ql
ql/cpp/ql/src/Diagnostics/FailedExtractorInvocations.ql
ql/cpp/ql/src/Likely Bugs/Arithmetic/BadAdditionOverflowCheck.ql
+ql/cpp/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql
ql/cpp/ql/src/Likely Bugs/Arithmetic/SignedOverflowCheck.ql
ql/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql
ql/cpp/ql/src/Likely Bugs/Format/SnprintfOverflow.ql
From 201af3fffcb9a79fbc49bc37ef05275abd0ce7f5 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Tue, 7 Apr 2026 12:59:31 +0100
Subject: [PATCH 26/33] C++: Update code scanning suite .expected.
---
.../integration-tests/query-suite/cpp-code-scanning.qls.expected | 1 +
1 file changed, 1 insertion(+)
diff --git a/cpp/ql/integration-tests/query-suite/cpp-code-scanning.qls.expected b/cpp/ql/integration-tests/query-suite/cpp-code-scanning.qls.expected
index 57d240fd795..926efb34d85 100644
--- a/cpp/ql/integration-tests/query-suite/cpp-code-scanning.qls.expected
+++ b/cpp/ql/integration-tests/query-suite/cpp-code-scanning.qls.expected
@@ -28,6 +28,7 @@ ql/cpp/ql/src/Security/CWE/CWE-120/VeryLikelyOverrunWrite.ql
ql/cpp/ql/src/Security/CWE/CWE-131/NoSpaceForZeroTerminator.ql
ql/cpp/ql/src/Security/CWE/CWE-134/UncontrolledFormatString.ql
ql/cpp/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql
+ql/cpp/ql/src/Security/CWE/CWE-190/ComparisonWithWiderType.ql
ql/cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql
ql/cpp/ql/src/Security/CWE/CWE-253/HResultBooleanConversion.ql
ql/cpp/ql/src/Security/CWE/CWE-311/CleartextFileWrite.ql
From 8d79248ea767a3e931d0e1695d48c8b4d0b719ae Mon Sep 17 00:00:00 2001
From: Taus
Date: Fri, 20 Mar 2026 13:55:49 +0000
Subject: [PATCH 27/33] Python: Port ModificationOfLocals.ql
---
python/ql/src/Statements/ModificationOfLocals.ql | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/python/ql/src/Statements/ModificationOfLocals.ql b/python/ql/src/Statements/ModificationOfLocals.ql
index e4791a410f7..82529cbd6d0 100644
--- a/python/ql/src/Statements/ModificationOfLocals.ql
+++ b/python/ql/src/Statements/ModificationOfLocals.ql
@@ -12,10 +12,10 @@
*/
import python
-private import LegacyPointsTo
+private import semmle.python.ApiGraphs
-predicate originIsLocals(ControlFlowNodeWithPointsTo n) {
- n.pointsTo(_, _, Value::named("locals").getACall())
+predicate originIsLocals(ControlFlowNode n) {
+ API::builtin("locals").getReturn().getAValueReachableFromSource().asCfgNode() = n
}
predicate modification_of_locals(ControlFlowNode f) {
@@ -37,5 +37,5 @@ where
// in module level scope `locals() == globals()`
// see https://docs.python.org/3/library/functions.html#locals
// FP report in https://github.com/github/codeql/issues/6674
- not a.getScope() instanceof ModuleScope
+ not a.getScope() instanceof Module
select a, "Modification of the locals() dictionary will have no effect on the local variables."
From e3688444d74582c29c0356f96ac80c19201f3166 Mon Sep 17 00:00:00 2001
From: Taus
Date: Tue, 7 Apr 2026 21:39:30 +0000
Subject: [PATCH 28/33] Python: Also exclude class scope
Changing the `locals()` dictionary actually _does_ change the attributes
of the class being defined, so we shouldn't alert in this case.
---
python/ql/src/Statements/ModificationOfLocals.ql | 5 ++++-
python/ql/test/query-tests/Statements/general/test.py | 6 ++++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/Statements/ModificationOfLocals.ql b/python/ql/src/Statements/ModificationOfLocals.ql
index 82529cbd6d0..f32ddcf7884 100644
--- a/python/ql/src/Statements/ModificationOfLocals.ql
+++ b/python/ql/src/Statements/ModificationOfLocals.ql
@@ -37,5 +37,8 @@ where
// in module level scope `locals() == globals()`
// see https://docs.python.org/3/library/functions.html#locals
// FP report in https://github.com/github/codeql/issues/6674
- not a.getScope() instanceof Module
+ not a.getScope() instanceof Module and
+ // in class level scope `locals()` reflects the class namespace,
+ // so modifications do take effect.
+ not a.getScope() instanceof Class
select a, "Modification of the locals() dictionary will have no effect on the local variables."
diff --git a/python/ql/test/query-tests/Statements/general/test.py b/python/ql/test/query-tests/Statements/general/test.py
index eee63fa89e8..a5848f7c718 100644
--- a/python/ql/test/query-tests/Statements/general/test.py
+++ b/python/ql/test/query-tests/Statements/general/test.py
@@ -174,3 +174,9 @@ def assert_ok(seq):
# False positive. ODASA-8042. Fixed in PR #2401.
class false_positive:
e = (x for x in [])
+
+# In class-level scope `locals()` reflects the class namespace,
+# so modifications do take effect.
+class MyClass:
+ locals()['x'] = 43 # OK
+ y = x
From d622dabf3e12ee9b6fb2b6167627e958943bf687 Mon Sep 17 00:00:00 2001
From: Taus
Date: Thu, 9 Apr 2026 13:06:45 +0000
Subject: [PATCH 29/33] Python: Add `create-extractor-pack.sh` for Python
This allows us to build and test the extractor (for actual QL extraction
-- not just the extractor unit tests) entirely from within the
`github/codeql` repo, just as we do with Ruby. All that's needed is a
`--search-path` argument that points to the repo root.
---
python/scripts/create-extractor-pack.sh | 67 +++++++++++++++++++++++++
1 file changed, 67 insertions(+)
create mode 100755 python/scripts/create-extractor-pack.sh
diff --git a/python/scripts/create-extractor-pack.sh b/python/scripts/create-extractor-pack.sh
new file mode 100755
index 00000000000..ae9248f6ccf
--- /dev/null
+++ b/python/scripts/create-extractor-pack.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+#
+# Build a local Python extractor pack from source.
+#
+# Usage with the CodeQL CLI (run from the repository root):
+#
+# codeql database create -l python -s --search-path .
+# codeql test run --search-path . python/ql/test/
+#
+set -eux
+
+if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+ platform="linux64"
+elif [[ "$OSTYPE" == "darwin"* ]]; then
+ platform="osx64"
+else
+ echo "Unknown OS"
+ exit 1
+fi
+
+cd "$(dirname "$0")/.."
+
+# Build the tsg-python Rust binary
+(cd extractor/tsg-python && cargo build --release)
+tsg_bin="extractor/tsg-python/target/release/tsg-python"
+
+# Generate python3src.zip from the Python extractor source.
+# make_zips.py creates the zip in the source directory and then copies it to the
+# given output directory. We use a temporary directory to avoid a same-file copy
+# error, then move the zip back.
+tmpdir=$(mktemp -d)
+trap 'rm -rf "$tmpdir"' EXIT
+(cd extractor && python3 make_zips.py "$tmpdir")
+cp "$tmpdir/python3src.zip" extractor/python3src.zip
+
+# Assemble the extractor pack
+rm -rf extractor-pack
+mkdir -p extractor-pack/tools/${platform}
+
+# Root-level metadata and schema files
+cp codeql-extractor.yml extractor-pack/
+cp ql/lib/semmlecode.python.dbscheme extractor-pack/
+cp ql/lib/semmlecode.python.dbscheme.stats extractor-pack/
+
+# Python extractor engine files (into tools/)
+cp extractor/python_tracer.py extractor-pack/tools/
+cp extractor/index.py extractor-pack/tools/
+cp extractor/setup.py extractor-pack/tools/
+cp extractor/convert_setup.py extractor-pack/tools/
+cp extractor/get_venv_lib.py extractor-pack/tools/
+cp extractor/imp.py extractor-pack/tools/
+cp extractor/LICENSE-PSF.md extractor-pack/tools/
+cp extractor/python3src.zip extractor-pack/tools/
+cp -r extractor/data extractor-pack/tools/
+
+# Shell tool scripts (autobuild, pre-finalize, lgtm-scripts)
+cp tools/autobuild.sh extractor-pack/tools/
+cp tools/autobuild.cmd extractor-pack/tools/
+cp tools/pre-finalize.sh extractor-pack/tools/
+cp tools/pre-finalize.cmd extractor-pack/tools/
+cp -r tools/lgtm-scripts extractor-pack/tools/
+
+# Downgrades
+cp -r downgrades extractor-pack/
+
+# Platform-specific Rust binary
+cp "${tsg_bin}" extractor-pack/tools/${platform}/tsg-python
From bee39c9d51baf22e0ad600077ce06aaaa98abece Mon Sep 17 00:00:00 2001
From: Jeroen Ketema
Date: Mon, 13 Apr 2026 11:08:58 +0200
Subject: [PATCH 30/33] C++: Fix `isCompiledAsC` join order
Before on Abseil Windows for `cpp/too-few-arguments:`:
```
Pipeline standard for TooFewArguments::isCompiledAsC/1#52fe29e8@994f9bgp was evaluated in 12 iterations totaling 2ms (delta sizes total: 50).
1198778 ~3% {1} r1 = JOIN `TooFewArguments::isCompiledAsC/1#52fe29e8#prev_delta` WITH `Element::Element.getFile/0#2b8c8740_10#join_rhs` ON FIRST 1 OUTPUT Rhs.1
83 ~26% {1} | JOIN WITH includes ON FIRST 1 OUTPUT Rhs.1
50 ~4% {1} | AND NOT `TooFewArguments::isCompiledAsC/1#52fe29e8#prev`(FIRST 1)
return r1
```
After:
```
Pipeline standard for #File::File.getAnIncludedFile/0#dispred#e8d44cd1Plus#bf@b8d290i6 was evaluated in 11 iterations totaling 0ms (delta sizes total: 43).
47 ~0% {2} r1 = SCAN `#File::File.getAnIncludedFile/0#dispred#e8d44cd1Plus#bf#prev_delta` OUTPUT In.1, In.0
78 ~28% {2} | JOIN WITH `File::File.getAnIncludedFile/0#dispred#e8d44cd1` ON FIRST 1 OUTPUT Lhs.1, Rhs.1
43 ~0% {2} | AND NOT `#File::File.getAnIncludedFile/0#dispred#e8d44cd1Plus#bf#prev`(FIRST 2)
return r1
[2026-04-13 11:05:25] Evaluated non-recursive predicate TooFewArguments::isCompiledAsC/1#52fe29e8@4a3eb9jk in 0ms (size: 49).
Evaluated relational algebra for predicate TooFewArguments::isCompiledAsC/1#52fe29e8@4a3eb9jk with tuple counts:
1 ~0% {3} r1 = CONSTANT(unique int, unique string, unique string)[1,"compiled as c","1"]
1 ~0% {1} | JOIN WITH #fileannotationsMerge_1230#join_rhs ON FIRST 3 OUTPUT Rhs.3
48 ~0% {1} r2 = JOIN r1 WITH `#File::File.getAnIncludedFile/0#dispred#e8d44cd1Plus#bf` ON FIRST 1 OUTPUT Rhs.1
49 ~0% {1} r3 = r1 UNION r2
return r3
```
---
.../Underspecified Functions/MistypedFunctionArguments.qll | 4 +---
.../Likely Bugs/Underspecified Functions/TooFewArguments.qll | 4 +---
.../Likely Bugs/Underspecified Functions/TooManyArguments.qll | 4 +---
3 files changed, 3 insertions(+), 9 deletions(-)
diff --git a/cpp/ql/src/Likely Bugs/Underspecified Functions/MistypedFunctionArguments.qll b/cpp/ql/src/Likely Bugs/Underspecified Functions/MistypedFunctionArguments.qll
index 2dced5d8d84..dbb457db505 100644
--- a/cpp/ql/src/Likely Bugs/Underspecified Functions/MistypedFunctionArguments.qll
+++ b/cpp/ql/src/Likely Bugs/Underspecified Functions/MistypedFunctionArguments.qll
@@ -79,9 +79,7 @@ private predicate hasZeroParamDecl(Function f) {
// True if this file (or header) was compiled as a C file
private predicate isCompiledAsC(File f) {
- f.compiledAsC()
- or
- exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f)
+ exists(File src | src.compiledAsC() | src.getAnIncludedFile*() = f)
}
predicate mistypedFunctionArguments(FunctionCall fc, Function f, Parameter p) {
diff --git a/cpp/ql/src/Likely Bugs/Underspecified Functions/TooFewArguments.qll b/cpp/ql/src/Likely Bugs/Underspecified Functions/TooFewArguments.qll
index 218a54b36c5..fd323513a49 100644
--- a/cpp/ql/src/Likely Bugs/Underspecified Functions/TooFewArguments.qll
+++ b/cpp/ql/src/Likely Bugs/Underspecified Functions/TooFewArguments.qll
@@ -28,9 +28,7 @@ private predicate hasZeroParamDecl(Function f) {
/* Holds if this file (or header) was compiled as a C file. */
private predicate isCompiledAsC(File f) {
- f.compiledAsC()
- or
- exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f)
+ exists(File src | src.compiledAsC() | src.getAnIncludedFile*() = f)
}
/** Holds if `fc` is a call to `f` with too few arguments. */
diff --git a/cpp/ql/src/Likely Bugs/Underspecified Functions/TooManyArguments.qll b/cpp/ql/src/Likely Bugs/Underspecified Functions/TooManyArguments.qll
index 7fba78b5550..ab2a98ae3a5 100644
--- a/cpp/ql/src/Likely Bugs/Underspecified Functions/TooManyArguments.qll
+++ b/cpp/ql/src/Likely Bugs/Underspecified Functions/TooManyArguments.qll
@@ -19,9 +19,7 @@ private predicate hasZeroParamDecl(Function f) {
// True if this file (or header) was compiled as a C file
private predicate isCompiledAsC(File f) {
- f.compiledAsC()
- or
- exists(File src | isCompiledAsC(src) | src.getAnIncludedFile() = f)
+ exists(File src | src.compiledAsC() | src.getAnIncludedFile*() = f)
}
predicate tooManyArguments(FunctionCall fc, Function f) {
From fef582c858b30ee7542a0d5d07a0ec0161278d09 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Mon, 13 Apr 2026 11:22:49 +0200
Subject: [PATCH 31/33] JS: Add test case for Fastify per-route rate limiting
---
.../MissingRateLimiting.expected | 7 ++++++
.../Security/CWE-770/MissingRateLimit/tst.js | 22 +++++++++++++++++++
2 files changed, 29 insertions(+)
diff --git a/javascript/ql/test/query-tests/Security/CWE-770/MissingRateLimit/MissingRateLimiting.expected b/javascript/ql/test/query-tests/Security/CWE-770/MissingRateLimit/MissingRateLimiting.expected
index 3d7bc2954eb..95f55486600 100644
--- a/javascript/ql/test/query-tests/Security/CWE-770/MissingRateLimit/MissingRateLimiting.expected
+++ b/javascript/ql/test/query-tests/Security/CWE-770/MissingRateLimit/MissingRateLimiting.expected
@@ -1,3 +1,4 @@
+#select
| MissingRateLimiting.js:4:19:8:1 | functio ... ath);\\n} | This route handler performs $@, but is not rate-limited. | MissingRateLimiting.js:7:5:7:22 | res.sendFile(path) | a file system access |
| MissingRateLimiting.js:25:19:25:20 | f1 | This route handler performs $@, but is not rate-limited. | MissingRateLimiting.js:13:5:13:22 | res.sendFile(path) | a file system access |
| MissingRateLimiting.js:25:27:25:28 | f3 | This route handler performs $@, but is not rate-limited. | MissingRateLimiting.js:22:5:22:22 | res.sendFile(path) | a file system access |
@@ -9,3 +10,9 @@
| tst.js:64:25:64:63 | functio ... req); } | This route handler performs $@, but is not rate-limited. | tst.js:64:46:64:60 | verifyUser(req) | authorization |
| tst.js:76:25:76:53 | catchAs ... ndler1) | This route handler performs $@, but is not rate-limited. | tst.js:14:40:14:46 | login() | authorization |
| tst.js:88:24:88:40 | expensiveHandler1 | This route handler performs $@, but is not rate-limited. | tst.js:14:40:14:46 | login() | authorization |
+| tst.js:103:4:103:20 | expensiveHandler1 | This route handler performs $@, but is not rate-limited. | tst.js:14:40:14:46 | login() | authorization |
+| tst.js:110:4:110:20 | expensiveHandler1 | This route handler performs $@, but is not rate-limited. | tst.js:14:40:14:46 | login() | authorization |
+| tst.js:112:28:112:44 | expensiveHandler1 | This route handler performs $@, but is not rate-limited. | tst.js:14:40:14:46 | login() | authorization |
+testFailures
+| tst.js:103:4:103:20 | This route handler performs $@, but is not rate-limited. | Unexpected result: Alert |
+| tst.js:110:4:110:20 | This route handler performs $@, but is not rate-limited. | Unexpected result: Alert |
diff --git a/javascript/ql/test/query-tests/Security/CWE-770/MissingRateLimit/tst.js b/javascript/ql/test/query-tests/Security/CWE-770/MissingRateLimit/tst.js
index 4f778afef68..5b4312bbbe0 100644
--- a/javascript/ql/test/query-tests/Security/CWE-770/MissingRateLimit/tst.js
+++ b/javascript/ql/test/query-tests/Security/CWE-770/MissingRateLimit/tst.js
@@ -88,3 +88,25 @@ const fastifyApp = require('fastify')();
fastifyApp.get('/foo', expensiveHandler1); // $ Alert
fastifyApp.register(require('fastify-rate-limit'));
fastifyApp.get('/bar', expensiveHandler1);
+
+// Fastify per-route rate limiting via config.rateLimit
+const fastifyApp2 = require('fastify')();
+fastifyApp2.register(require('@fastify/rate-limit'));
+
+fastifyApp2.post('/login', {
+ config: {
+ rateLimit: {
+ max: 3,
+ timeWindow: '1 minute'
+ }
+ }
+}, expensiveHandler1); // OK - has per-route rateLimit config
+
+fastifyApp2.post('/signup', {
+ rateLimit: {
+ max: 5,
+ timeWindow: '1 minute'
+ }
+}, expensiveHandler1); // OK - has per-route rateLimit directly in options
+
+fastifyApp2.post('/other', expensiveHandler1); // $ Alert - no rate limiting
From 7a48409e386191f5fb0d95652bf0c431333c8d45 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Mon, 13 Apr 2026 11:23:08 +0200
Subject: [PATCH 32/33] JS: Recognize Fastify per-route rate limiting
---
.../security/dataflow/MissingRateLimiting.qll | 18 ++++++++++++++++++
.../MissingRateLimiting.expected | 6 ------
2 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/MissingRateLimiting.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/MissingRateLimiting.qll
index 5f4ad1b3d73..8dd9c483144 100644
--- a/javascript/ql/lib/semmle/javascript/security/dataflow/MissingRateLimiting.qll
+++ b/javascript/ql/lib/semmle/javascript/security/dataflow/MissingRateLimiting.qll
@@ -191,3 +191,21 @@ class RouteHandlerLimitedByRateLimiterFlexible extends RateLimitingMiddleware in
private class FastifyRateLimiter extends RateLimitingMiddleware {
FastifyRateLimiter() { this = DataFlow::moduleImport("fastify-rate-limit") }
}
+
+/**
+ * An options object with a `rateLimit` config passed to a Fastify shorthand route method,
+ * such as `fastify.post('/path', { config: { rateLimit: { ... } } }, handler)`.
+ */
+private class FastifyPerRouteRateLimit extends RateLimitingMiddleware {
+ FastifyPerRouteRateLimit() {
+ exists(Fastify::RouteSetup setup |
+ not setup.getMethodName() = ["route", "addHook"] and
+ setup.getNumArgument() >= 3 and
+ this.flowsTo(setup.getArgument(1))
+ |
+ exists(this.getAPropertySource("config").getAPropertySource("rateLimit"))
+ or
+ exists(this.getAPropertySource("rateLimit"))
+ )
+ }
+}
diff --git a/javascript/ql/test/query-tests/Security/CWE-770/MissingRateLimit/MissingRateLimiting.expected b/javascript/ql/test/query-tests/Security/CWE-770/MissingRateLimit/MissingRateLimiting.expected
index 95f55486600..8d197d6e37f 100644
--- a/javascript/ql/test/query-tests/Security/CWE-770/MissingRateLimit/MissingRateLimiting.expected
+++ b/javascript/ql/test/query-tests/Security/CWE-770/MissingRateLimit/MissingRateLimiting.expected
@@ -1,4 +1,3 @@
-#select
| MissingRateLimiting.js:4:19:8:1 | functio ... ath);\\n} | This route handler performs $@, but is not rate-limited. | MissingRateLimiting.js:7:5:7:22 | res.sendFile(path) | a file system access |
| MissingRateLimiting.js:25:19:25:20 | f1 | This route handler performs $@, but is not rate-limited. | MissingRateLimiting.js:13:5:13:22 | res.sendFile(path) | a file system access |
| MissingRateLimiting.js:25:27:25:28 | f3 | This route handler performs $@, but is not rate-limited. | MissingRateLimiting.js:22:5:22:22 | res.sendFile(path) | a file system access |
@@ -10,9 +9,4 @@
| tst.js:64:25:64:63 | functio ... req); } | This route handler performs $@, but is not rate-limited. | tst.js:64:46:64:60 | verifyUser(req) | authorization |
| tst.js:76:25:76:53 | catchAs ... ndler1) | This route handler performs $@, but is not rate-limited. | tst.js:14:40:14:46 | login() | authorization |
| tst.js:88:24:88:40 | expensiveHandler1 | This route handler performs $@, but is not rate-limited. | tst.js:14:40:14:46 | login() | authorization |
-| tst.js:103:4:103:20 | expensiveHandler1 | This route handler performs $@, but is not rate-limited. | tst.js:14:40:14:46 | login() | authorization |
-| tst.js:110:4:110:20 | expensiveHandler1 | This route handler performs $@, but is not rate-limited. | tst.js:14:40:14:46 | login() | authorization |
| tst.js:112:28:112:44 | expensiveHandler1 | This route handler performs $@, but is not rate-limited. | tst.js:14:40:14:46 | login() | authorization |
-testFailures
-| tst.js:103:4:103:20 | This route handler performs $@, but is not rate-limited. | Unexpected result: Alert |
-| tst.js:110:4:110:20 | This route handler performs $@, but is not rate-limited. | Unexpected result: Alert |
From fcfb8c9c6bc5e76f877b9d5748a68a1d2084889c Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Mon, 13 Apr 2026 12:22:30 +0200
Subject: [PATCH 33/33] Add change note
---
.../change-notes/2026-04-13-fastify-per-route-rate-limit.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 javascript/ql/src/change-notes/2026-04-13-fastify-per-route-rate-limit.md
diff --git a/javascript/ql/src/change-notes/2026-04-13-fastify-per-route-rate-limit.md b/javascript/ql/src/change-notes/2026-04-13-fastify-per-route-rate-limit.md
new file mode 100644
index 00000000000..56d52388524
--- /dev/null
+++ b/javascript/ql/src/change-notes/2026-04-13-fastify-per-route-rate-limit.md
@@ -0,0 +1,5 @@
+---
+category: minorAnalysis
+---
+* The query `js/missing-rate-limiting` now takes Fastify per-route
+ rate limiting into account.