From 6da1c01058c0ab699c6249d0b7ddb155003d502b Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 15 Apr 2026 11:48:45 +0100 Subject: [PATCH 1/2] PS: Add false positive with GUID. --- .../security/cwe-089/SqlInjection.expected | 16 ++++++++++++++++ .../test/query-tests/security/cwe-089/test.ps1 | 11 ++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/powershell/ql/test/query-tests/security/cwe-089/SqlInjection.expected b/powershell/ql/test/query-tests/security/cwe-089/SqlInjection.expected index 0c1da158aca4..a054de2983ae 100644 --- a/powershell/ql/test/query-tests/security/cwe-089/SqlInjection.expected +++ b/powershell/ql/test/query-tests/security/cwe-089/SqlInjection.expected @@ -19,12 +19,20 @@ edges | test.ps1:78:49:78:58 | userinput | test.ps1:78:13:78:59 | SELECT * FROM Customers WHERE id = $userinput | provenance | Config | | test.ps1:78:49:78:58 | userinput | test.ps1:111:51:111:60 | userinput | provenance | | | test.ps1:111:51:111:60 | userinput | test.ps1:128:28:128:37 | userinput | provenance | | +| test.ps1:111:51:111:60 | userinput | test.ps1:150:10:150:19 | userinput | provenance | | | test.ps1:121:9:121:56 | unvalidated | test.ps1:125:130:125:141 | unvalidated | provenance | | | test.ps1:125:128:125:142 | $(...) | test.ps1:125:92:125:143 | SELECT * FROM Customers where id = $($unvalidated) | provenance | | | test.ps1:125:128:125:142 | $(...) | test.ps1:125:92:125:143 | SELECT * FROM Customers where id = $($unvalidated) | provenance | Config | | test.ps1:125:130:125:141 | unvalidated | test.ps1:125:128:125:142 | $(...) | provenance | | | test.ps1:125:130:125:141 | unvalidated | test.ps1:125:128:125:142 | $(...) | provenance | Config | | test.ps1:128:28:128:37 | userinput | test.ps1:121:9:121:56 | unvalidated | provenance | | +| test.ps1:144:11:144:50 | r | test.ps1:146:55:146:56 | r | provenance | | +| test.ps1:146:5:146:10 | query | test.ps1:147:72:147:77 | query | provenance | | +| test.ps1:146:5:146:10 | query | test.ps1:147:72:147:77 | query | provenance | | +| test.ps1:146:14:146:58 | SELECT * FROM MyTable WHERE MyColumn = '$r' | test.ps1:146:5:146:10 | query | provenance | | +| test.ps1:146:14:146:58 | SELECT * FROM MyTable WHERE MyColumn = '$r' | test.ps1:146:5:146:10 | query | provenance | | +| test.ps1:146:55:146:56 | r | test.ps1:146:14:146:58 | SELECT * FROM MyTable WHERE MyColumn = '$r' | provenance | Config | +| test.ps1:150:10:150:19 | userinput | test.ps1:144:11:144:50 | r | provenance | | nodes | test.ps1:1:1:1:10 | userinput | semmle.label | userinput | | test.ps1:1:14:1:45 | Call to read-host | semmle.label | Call to read-host | @@ -52,6 +60,13 @@ nodes | test.ps1:125:128:125:142 | $(...) | semmle.label | $(...) | | test.ps1:125:130:125:141 | unvalidated | semmle.label | unvalidated | | test.ps1:128:28:128:37 | userinput | semmle.label | userinput | +| test.ps1:144:11:144:50 | r | semmle.label | r | +| test.ps1:146:5:146:10 | query | semmle.label | query | +| test.ps1:146:5:146:10 | query | semmle.label | query | +| test.ps1:146:14:146:58 | SELECT * FROM MyTable WHERE MyColumn = '$r' | semmle.label | SELECT * FROM MyTable WHERE MyColumn = '$r' | +| test.ps1:146:55:146:56 | r | semmle.label | r | +| test.ps1:147:72:147:77 | query | semmle.label | query | +| test.ps1:150:10:150:19 | userinput | semmle.label | userinput | subpaths #select | test.ps1:5:72:5:77 | query | test.ps1:1:14:1:45 | Call to read-host | test.ps1:5:72:5:77 | query | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin | @@ -60,3 +75,4 @@ subpaths | test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | test.ps1:1:14:1:45 | Call to read-host | test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin | | test.ps1:81:15:81:25 | QueryConn2 | test.ps1:1:14:1:45 | Call to read-host | test.ps1:81:15:81:25 | QueryConn2 | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin | | test.ps1:125:92:125:143 | SELECT * FROM Customers where id = $($unvalidated) | test.ps1:1:14:1:45 | Call to read-host | test.ps1:125:92:125:143 | SELECT * FROM Customers where id = $($unvalidated) | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin | +| test.ps1:147:72:147:77 | query | test.ps1:1:14:1:45 | Call to read-host | test.ps1:147:72:147:77 | query | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin | diff --git a/powershell/ql/test/query-tests/security/cwe-089/test.ps1 b/powershell/ql/test/query-tests/security/cwe-089/test.ps1 index 2c023aa26f36..b95f9f2aa5ad 100644 --- a/powershell/ql/test/query-tests/security/cwe-089/test.ps1 +++ b/powershell/ql/test/query-tests/security/cwe-089/test.ps1 @@ -138,4 +138,13 @@ $QueryConn3 = @{ Invoke-Sqlcmd @QueryConn3 # GOOD -&sqlcmd -e -S $userinput -U "Login" -P "MyPassword" -d "MyDBName" -i "input_file.sql" # GOOD \ No newline at end of file +&sqlcmd -e -S $userinput -U "Login" -P "MyPassword" -d "MyDBName" -i "input_file.sql" # GOOD + +function WithGuid { + PARAM([Parameter(Mandatory = $true)] [guid] $r) + + $query = "SELECT * FROM MyTable WHERE MyColumn = '$r'" + Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -q $query # GOOD [FALSE POSITIVE] +} + +WithGuid $userinput \ No newline at end of file From 6465ecddba61ca11e41aa4c2f1b09bd253c0bb44 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 15 Apr 2026 11:52:46 +0100 Subject: [PATCH 2/2] PS: Add GUIDs as a simple sanitizer, and accept test changes. --- .../code/powershell/security/Sanitizers.qll | 4 ++-- .../security/cwe-089/SqlInjection.expected | 16 ---------------- .../test/query-tests/security/cwe-089/test.ps1 | 2 +- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/powershell/ql/lib/semmle/code/powershell/security/Sanitizers.qll b/powershell/ql/lib/semmle/code/powershell/security/Sanitizers.qll index 29b82e5030a6..10d05a9d515e 100644 --- a/powershell/ql/lib/semmle/code/powershell/security/Sanitizers.qll +++ b/powershell/ql/lib/semmle/code/powershell/security/Sanitizers.qll @@ -4,11 +4,11 @@ private import semmle.code.powershell.dataflow.DataFlow /** * A dataflow node that is guarenteed to have a "simple" type. * - * Simple types include integers, floats, characters, booleans, and `datetime`. + * Simple types include integers, floats, characters, booleans, `datetime`, and `guid`. */ class SimpleTypeSanitizer extends DataFlow::Node { SimpleTypeSanitizer() { this.asParameter().getStaticType() = - ["int32", "int64", "single", "double", "decimal", "char", "boolean", "datetime"] + ["int32", "int64", "single", "double", "decimal", "char", "boolean", "datetime", "guid"] } } diff --git a/powershell/ql/test/query-tests/security/cwe-089/SqlInjection.expected b/powershell/ql/test/query-tests/security/cwe-089/SqlInjection.expected index a054de2983ae..0c1da158aca4 100644 --- a/powershell/ql/test/query-tests/security/cwe-089/SqlInjection.expected +++ b/powershell/ql/test/query-tests/security/cwe-089/SqlInjection.expected @@ -19,20 +19,12 @@ edges | test.ps1:78:49:78:58 | userinput | test.ps1:78:13:78:59 | SELECT * FROM Customers WHERE id = $userinput | provenance | Config | | test.ps1:78:49:78:58 | userinput | test.ps1:111:51:111:60 | userinput | provenance | | | test.ps1:111:51:111:60 | userinput | test.ps1:128:28:128:37 | userinput | provenance | | -| test.ps1:111:51:111:60 | userinput | test.ps1:150:10:150:19 | userinput | provenance | | | test.ps1:121:9:121:56 | unvalidated | test.ps1:125:130:125:141 | unvalidated | provenance | | | test.ps1:125:128:125:142 | $(...) | test.ps1:125:92:125:143 | SELECT * FROM Customers where id = $($unvalidated) | provenance | | | test.ps1:125:128:125:142 | $(...) | test.ps1:125:92:125:143 | SELECT * FROM Customers where id = $($unvalidated) | provenance | Config | | test.ps1:125:130:125:141 | unvalidated | test.ps1:125:128:125:142 | $(...) | provenance | | | test.ps1:125:130:125:141 | unvalidated | test.ps1:125:128:125:142 | $(...) | provenance | Config | | test.ps1:128:28:128:37 | userinput | test.ps1:121:9:121:56 | unvalidated | provenance | | -| test.ps1:144:11:144:50 | r | test.ps1:146:55:146:56 | r | provenance | | -| test.ps1:146:5:146:10 | query | test.ps1:147:72:147:77 | query | provenance | | -| test.ps1:146:5:146:10 | query | test.ps1:147:72:147:77 | query | provenance | | -| test.ps1:146:14:146:58 | SELECT * FROM MyTable WHERE MyColumn = '$r' | test.ps1:146:5:146:10 | query | provenance | | -| test.ps1:146:14:146:58 | SELECT * FROM MyTable WHERE MyColumn = '$r' | test.ps1:146:5:146:10 | query | provenance | | -| test.ps1:146:55:146:56 | r | test.ps1:146:14:146:58 | SELECT * FROM MyTable WHERE MyColumn = '$r' | provenance | Config | -| test.ps1:150:10:150:19 | userinput | test.ps1:144:11:144:50 | r | provenance | | nodes | test.ps1:1:1:1:10 | userinput | semmle.label | userinput | | test.ps1:1:14:1:45 | Call to read-host | semmle.label | Call to read-host | @@ -60,13 +52,6 @@ nodes | test.ps1:125:128:125:142 | $(...) | semmle.label | $(...) | | test.ps1:125:130:125:141 | unvalidated | semmle.label | unvalidated | | test.ps1:128:28:128:37 | userinput | semmle.label | userinput | -| test.ps1:144:11:144:50 | r | semmle.label | r | -| test.ps1:146:5:146:10 | query | semmle.label | query | -| test.ps1:146:5:146:10 | query | semmle.label | query | -| test.ps1:146:14:146:58 | SELECT * FROM MyTable WHERE MyColumn = '$r' | semmle.label | SELECT * FROM MyTable WHERE MyColumn = '$r' | -| test.ps1:146:55:146:56 | r | semmle.label | r | -| test.ps1:147:72:147:77 | query | semmle.label | query | -| test.ps1:150:10:150:19 | userinput | semmle.label | userinput | subpaths #select | test.ps1:5:72:5:77 | query | test.ps1:1:14:1:45 | Call to read-host | test.ps1:5:72:5:77 | query | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin | @@ -75,4 +60,3 @@ subpaths | test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | test.ps1:1:14:1:45 | Call to read-host | test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin | | test.ps1:81:15:81:25 | QueryConn2 | test.ps1:1:14:1:45 | Call to read-host | test.ps1:81:15:81:25 | QueryConn2 | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin | | test.ps1:125:92:125:143 | SELECT * FROM Customers where id = $($unvalidated) | test.ps1:1:14:1:45 | Call to read-host | test.ps1:125:92:125:143 | SELECT * FROM Customers where id = $($unvalidated) | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin | -| test.ps1:147:72:147:77 | query | test.ps1:1:14:1:45 | Call to read-host | test.ps1:147:72:147:77 | query | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin | diff --git a/powershell/ql/test/query-tests/security/cwe-089/test.ps1 b/powershell/ql/test/query-tests/security/cwe-089/test.ps1 index b95f9f2aa5ad..c2aee3af258e 100644 --- a/powershell/ql/test/query-tests/security/cwe-089/test.ps1 +++ b/powershell/ql/test/query-tests/security/cwe-089/test.ps1 @@ -144,7 +144,7 @@ function WithGuid { PARAM([Parameter(Mandatory = $true)] [guid] $r) $query = "SELECT * FROM MyTable WHERE MyColumn = '$r'" - Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -q $query # GOOD [FALSE POSITIVE] + Invoke-Sqlcmd -ServerInstance "MyServer" -Database "MyDatabase" -q $query # GOOD } WithGuid $userinput \ No newline at end of file