From 3dc465f167536d9a6ece5044bebc2eaedd6e00f2 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 17 Feb 2026 09:57:14 +0000 Subject: [PATCH 01/16] Accept MaD sanitizers for queries with MaD sinks --- .../lib/codeql/ruby/security/CodeInjectionCustomizations.qll | 4 ++++ .../codeql/ruby/security/CommandInjectionCustomizations.qll | 4 ++++ ruby/ql/lib/codeql/ruby/security/LogInjectionQuery.qll | 4 ++++ .../lib/codeql/ruby/security/PathInjectionCustomizations.qll | 4 ++++ .../ruby/security/ServerSideRequestForgeryCustomizations.qll | 4 ++++ .../lib/codeql/ruby/security/SqlInjectionCustomizations.qll | 4 ++++ .../ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll | 4 ++++ 7 files changed, 28 insertions(+) diff --git a/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll index ca79a079a107..0e84aa710b5b 100644 --- a/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/CodeInjectionCustomizations.qll @@ -118,4 +118,8 @@ module CodeInjection { private class ExternalCodeInjectionSink extends Sink { ExternalCodeInjectionSink() { ModelOutput::sinkNode(this, "code-injection") } } + + private class ExternalCodeInjectionSanitizer extends Sanitizer { + ExternalCodeInjectionSanitizer() { ModelOutput::barrierNode(this, "code-injection") } + } } diff --git a/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll index f36b72ae6b79..d9551177875c 100644 --- a/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll @@ -57,4 +57,8 @@ module CommandInjection { private class ExternalCommandInjectionSink extends Sink { ExternalCommandInjectionSink() { ModelOutput::sinkNode(this, "command-injection") } } + + private class ExternalCommandInjectionSanitizer extends Sanitizer { + ExternalCommandInjectionSanitizer() { ModelOutput::barrierNode(this, "command-injection") } + } } diff --git a/ruby/ql/lib/codeql/ruby/security/LogInjectionQuery.qll b/ruby/ql/lib/codeql/ruby/security/LogInjectionQuery.qll index 8111932c7df4..a5230a8b8450 100644 --- a/ruby/ql/lib/codeql/ruby/security/LogInjectionQuery.qll +++ b/ruby/ql/lib/codeql/ruby/security/LogInjectionQuery.qll @@ -67,6 +67,10 @@ class HtmlEscapingAsSanitizer extends Sanitizer { HtmlEscapingAsSanitizer() { this = any(HtmlEscaping esc).getOutput() } } +private class ExternalLogInjectionSanitizer extends Sanitizer { + ExternalLogInjectionSanitizer() { ModelOutput::barrierNode(this, "log-injection") } +} + private module LogInjectionConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { source instanceof Source } diff --git a/ruby/ql/lib/codeql/ruby/security/PathInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/PathInjectionCustomizations.qll index 8a8b916f6275..beab1af5dc4c 100644 --- a/ruby/ql/lib/codeql/ruby/security/PathInjectionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/PathInjectionCustomizations.qll @@ -57,4 +57,8 @@ module PathInjection { private class ExternalPathInjectionSink extends Sink { ExternalPathInjectionSink() { ModelOutput::sinkNode(this, "path-injection") } } + + private class ExternalPathInjectionSanitizer extends Sanitizer { + ExternalPathInjectionSanitizer() { ModelOutput::barrierNode(this, "path-injection") } + } } diff --git a/ruby/ql/lib/codeql/ruby/security/ServerSideRequestForgeryCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/ServerSideRequestForgeryCustomizations.qll index 509900a12e15..e64abe413b8f 100644 --- a/ruby/ql/lib/codeql/ruby/security/ServerSideRequestForgeryCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/ServerSideRequestForgeryCustomizations.qll @@ -46,4 +46,8 @@ module ServerSideRequestForgery { private class ExternalRequestForgerySink extends Sink { ExternalRequestForgerySink() { ModelOutput::sinkNode(this, "request-forgery") } } + + private class ExternalRequestForgerySanitizer extends Sanitizer { + ExternalRequestForgerySanitizer() { ModelOutput::barrierNode(this, "request-forgery") } + } } diff --git a/ruby/ql/lib/codeql/ruby/security/SqlInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/SqlInjectionCustomizations.qll index 1bf14dc3b280..7d6f16731a56 100644 --- a/ruby/ql/lib/codeql/ruby/security/SqlInjectionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/SqlInjectionCustomizations.qll @@ -61,4 +61,8 @@ module SqlInjection { private class ExternalSqlInjectionSink extends Sink { ExternalSqlInjectionSink() { ModelOutput::sinkNode(this, "sql-injection") } } + + private class ExternalSqlInjectionSanitizer extends Sanitizer { + ExternalSqlInjectionSanitizer() { ModelOutput::barrierNode(this, "sql-injection") } + } } diff --git a/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll index 4e02b3181e35..0cef83070a64 100644 --- a/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/UrlRedirectCustomizations.qll @@ -125,6 +125,10 @@ module UrlRedirect { */ class StringInterpolationAsSanitizer extends PrefixedStringInterpolation, Sanitizer { } + private class ExternalUrlRedirectSanitizer extends Sanitizer { + ExternalUrlRedirectSanitizer() { ModelOutput::barrierNode(this, "url-redirection") } + } + /** * These methods return a new `ActionController::Parameters` or a `Hash` containing a subset of * the original values. This may still contain user input, so the results are tainted. From 1d7a39a093a748276b454645ecd8be7703ed97d6 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 17 Feb 2026 12:58:38 +0000 Subject: [PATCH 02/16] Change how sql-injection barriers are accepted --- ruby/ql/lib/codeql/ruby/Concepts.qll | 5 +++++ .../lib/codeql/ruby/security/SqlInjectionCustomizations.qll | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/Concepts.qll b/ruby/ql/lib/codeql/ruby/Concepts.qll index 2ddcb433e1be..642a2fd0b1bf 100644 --- a/ruby/ql/lib/codeql/ruby/Concepts.qll +++ b/ruby/ql/lib/codeql/ruby/Concepts.qll @@ -9,6 +9,7 @@ private import codeql.ruby.CFG private import codeql.ruby.DataFlow private import codeql.ruby.dataflow.internal.DataFlowImplSpecific private import codeql.ruby.Frameworks +private import codeql.ruby.frameworks.data.internal.ApiGraphModels private import codeql.ruby.dataflow.RemoteFlowSources private import codeql.ruby.ApiGraphs private import codeql.ruby.Regexp as RE @@ -95,6 +96,10 @@ module SqlSanitization { abstract class Range extends DataFlow::Node { } } +private class ExternalSqlInjectionSanitizer extends SqlSanitization::Range { + ExternalSqlInjectionSanitizer() { ModelOutput::barrierNode(this, "sql-injection") } +} + /** * A data-flow node that executes a regular expression. * diff --git a/ruby/ql/lib/codeql/ruby/security/SqlInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/SqlInjectionCustomizations.qll index 7d6f16731a56..1bf14dc3b280 100644 --- a/ruby/ql/lib/codeql/ruby/security/SqlInjectionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/SqlInjectionCustomizations.qll @@ -61,8 +61,4 @@ module SqlInjection { private class ExternalSqlInjectionSink extends Sink { ExternalSqlInjectionSink() { ModelOutput::sinkNode(this, "sql-injection") } } - - private class ExternalSqlInjectionSanitizer extends Sanitizer { - ExternalSqlInjectionSanitizer() { ModelOutput::barrierNode(this, "sql-injection") } - } } From fc429c175799f150470f6dbdbaa8e20664187807 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 17 Feb 2026 15:30:10 +0000 Subject: [PATCH 03/16] Improve Mysql2 test --- .../library-tests/frameworks/mysql2/Mysql2.rb | 8 ++++---- .../frameworks/mysql2/SqlInjection.expected | 15 +++++++++++++++ .../frameworks/mysql2/SqlInjection.qlref | 4 ++++ 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.expected create mode 100644 ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.qlref diff --git a/ruby/ql/test/library-tests/frameworks/mysql2/Mysql2.rb b/ruby/ql/test/library-tests/frameworks/mysql2/Mysql2.rb index b9b3b5a7b575..1294f1614757 100644 --- a/ruby/ql/test/library-tests/frameworks/mysql2/Mysql2.rb +++ b/ruby/ql/test/library-tests/frameworks/mysql2/Mysql2.rb @@ -1,6 +1,6 @@ class UsersController < ActionController::Base def mysql2_handler(event:, context:) - name = params[:user_name] + name = params[:user_name] # $ Source[rb/sql-injection] conn = Mysql2::Client.new( host: "127.0.0.1", @@ -10,7 +10,7 @@ def mysql2_handler(event:, context:) results1 = conn.query("SELECT * FROM users") # BAD: SQL statement constructed from user input - results2 = conn.query("SELECT * FROM users WHERE username='#{name}'") + results2 = conn.query("SELECT * FROM users WHERE username='#{name}'") # $ Alert[rb/sql-injection] # GOOD: user input is escaped escaped = Mysql2::Client.escape(name) @@ -21,10 +21,10 @@ def mysql2_handler(event:, context:) results4 = statement1.execute(1, name, :as => :array) # BAD: SQL statement constructed from user input - statement2 = conn.prepare("SELECT * FROM users WHERE username='#{name}' AND password = ?") + statement2 = conn.prepare("SELECT * FROM users WHERE username='#{name}' AND password = ?") # $ Alert[rb/sql-injection] results4 = statement2.execute("password", :as => :array) # NOT EXECUTED statement3 = conn.prepare("SELECT * FROM users WHERE username = ?") end -end \ No newline at end of file +end diff --git a/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.expected b/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.expected new file mode 100644 index 000000000000..f29b1738394e --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.expected @@ -0,0 +1,15 @@ +#select +| Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." | Mysql2.rb:3:12:3:17 | call to params | Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." | This SQL query depends on a $@. | Mysql2.rb:3:12:3:17 | call to params | user-provided value | +| Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." | Mysql2.rb:3:12:3:17 | call to params | Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." | This SQL query depends on a $@. | Mysql2.rb:3:12:3:17 | call to params | user-provided value | +edges +| Mysql2.rb:3:5:3:8 | name | Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." | provenance | AdditionalTaintStep | +| Mysql2.rb:3:5:3:8 | name | Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." | provenance | AdditionalTaintStep | +| Mysql2.rb:3:12:3:17 | call to params | Mysql2.rb:3:12:3:29 | ...[...] | provenance | | +| Mysql2.rb:3:12:3:29 | ...[...] | Mysql2.rb:3:5:3:8 | name | provenance | | +nodes +| Mysql2.rb:3:5:3:8 | name | semmle.label | name | +| Mysql2.rb:3:12:3:17 | call to params | semmle.label | call to params | +| Mysql2.rb:3:12:3:29 | ...[...] | semmle.label | ...[...] | +| Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." | semmle.label | "SELECT * FROM users WHERE use..." | +| Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." | semmle.label | "SELECT * FROM users WHERE use..." | +subpaths diff --git a/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.qlref b/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.qlref new file mode 100644 index 000000000000..ff400a718e22 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.qlref @@ -0,0 +1,4 @@ +query: queries/security/cwe-089/SqlInjection.ql +postprocess: + - utils/test/PrettyPrintModels.ql + - utils/test/InlineExpectationsTestQuery.ql From 3e4f42f8a3c5117e16f746446aa60abe898a1b69 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 17 Feb 2026 15:40:10 +0000 Subject: [PATCH 04/16] Move Mysql2 flow model to MaD and remove ql sanitizer --- .../codeql/ruby/frameworks/Mysql2.model.yml | 6 +++++ ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll | 22 ------------------- .../frameworks/mysql2/SqlInjection.expected | 13 +++++++++++ 3 files changed, 19 insertions(+), 22 deletions(-) create mode 100644 ruby/ql/lib/codeql/ruby/frameworks/Mysql2.model.yml diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.model.yml b/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.model.yml new file mode 100644 index 000000000000..c0a20d0d32d2 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.model.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/ruby-all + extensible: summaryModel + data: + - ['Mysql2::Client!', 'Method[escape]', 'Argument[0]', 'ReturnValue', 'taint'] diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll b/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll index baca5bba95f3..67cf762f9850 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.qll @@ -48,26 +48,4 @@ module Mysql2 { override DataFlow::Node getSql() { result = query } } - - /** - * A call to `Mysql2::Client.escape`, considered as a sanitizer for SQL statements. - */ - private class Mysql2EscapeSanitization extends SqlSanitization::Range { - Mysql2EscapeSanitization() { - this = API::getTopLevelMember("Mysql2").getMember("Client").getAMethodCall("escape") - } - } - - /** - * Flow summary for `Mysql2::Client.escape()`. - */ - private class EscapeSummary extends SummarizedCallable::Range { - EscapeSummary() { this = "Mysql2::Client.escape()" } - - override MethodCall getACall() { result = any(Mysql2EscapeSanitization c).asExpr().getExpr() } - - override predicate propagatesFlow(string input, string output, boolean preservesValue) { - input = "Argument[0]" and output = "ReturnValue" and preservesValue = false - } - } } diff --git a/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.expected b/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.expected index f29b1738394e..5f4905e2ccb7 100644 --- a/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.expected +++ b/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.expected @@ -1,15 +1,28 @@ #select | Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." | Mysql2.rb:3:12:3:17 | call to params | Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." | This SQL query depends on a $@. | Mysql2.rb:3:12:3:17 | call to params | user-provided value | +| Mysql2.rb:17:27:17:75 | "SELECT * FROM users WHERE use..." | Mysql2.rb:3:12:3:17 | call to params | Mysql2.rb:17:27:17:75 | "SELECT * FROM users WHERE use..." | This SQL query depends on a $@. | Mysql2.rb:3:12:3:17 | call to params | user-provided value | | Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." | Mysql2.rb:3:12:3:17 | call to params | Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." | This SQL query depends on a $@. | Mysql2.rb:3:12:3:17 | call to params | user-provided value | edges | Mysql2.rb:3:5:3:8 | name | Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." | provenance | AdditionalTaintStep | +| Mysql2.rb:3:5:3:8 | name | Mysql2.rb:16:37:16:40 | name | provenance | | | Mysql2.rb:3:5:3:8 | name | Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." | provenance | AdditionalTaintStep | | Mysql2.rb:3:12:3:17 | call to params | Mysql2.rb:3:12:3:29 | ...[...] | provenance | | | Mysql2.rb:3:12:3:29 | ...[...] | Mysql2.rb:3:5:3:8 | name | provenance | | +| Mysql2.rb:16:5:16:11 | escaped | Mysql2.rb:17:27:17:75 | "SELECT * FROM users WHERE use..." | provenance | AdditionalTaintStep | +| Mysql2.rb:16:15:16:41 | call to escape | Mysql2.rb:16:5:16:11 | escaped | provenance | | +| Mysql2.rb:16:37:16:40 | name | Mysql2.rb:16:15:16:41 | call to escape | provenance | MaD:1 | +models +| 1 | Summary: Mysql2::Client!; Method[escape]; Argument[0]; ReturnValue; taint | nodes | Mysql2.rb:3:5:3:8 | name | semmle.label | name | | Mysql2.rb:3:12:3:17 | call to params | semmle.label | call to params | | Mysql2.rb:3:12:3:29 | ...[...] | semmle.label | ...[...] | | Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." | semmle.label | "SELECT * FROM users WHERE use..." | +| Mysql2.rb:16:5:16:11 | escaped | semmle.label | escaped | +| Mysql2.rb:16:15:16:41 | call to escape | semmle.label | call to escape | +| Mysql2.rb:16:37:16:40 | name | semmle.label | name | +| Mysql2.rb:17:27:17:75 | "SELECT * FROM users WHERE use..." | semmle.label | "SELECT * FROM users WHERE use..." | | Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." | semmle.label | "SELECT * FROM users WHERE use..." | subpaths +testFailures +| Mysql2.rb:17:27:17:75 | "SELECT * FROM users WHERE use..." | Unexpected result: Alert | From d4bb92b038567a61b7f55fb10bff8729467c2f05 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 17 Feb 2026 15:42:21 +0000 Subject: [PATCH 05/16] Reinstate Mysql2 sanitizer in MaD --- ruby/ql/lib/codeql/ruby/frameworks/Mysql2.model.yml | 5 +++++ .../frameworks/mysql2/SqlInjection.expected | 13 ------------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.model.yml b/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.model.yml index c0a20d0d32d2..1b6c8c754f57 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.model.yml +++ b/ruby/ql/lib/codeql/ruby/frameworks/Mysql2.model.yml @@ -4,3 +4,8 @@ extensions: extensible: summaryModel data: - ['Mysql2::Client!', 'Method[escape]', 'Argument[0]', 'ReturnValue', 'taint'] + - addsTo: + pack: codeql/ruby-all + extensible: barrierModel + data: + - ['Mysql2::Client!', 'Method[escape].ReturnValue', 'sql-injection'] diff --git a/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.expected b/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.expected index 5f4905e2ccb7..f29b1738394e 100644 --- a/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.expected +++ b/ruby/ql/test/library-tests/frameworks/mysql2/SqlInjection.expected @@ -1,28 +1,15 @@ #select | Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." | Mysql2.rb:3:12:3:17 | call to params | Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." | This SQL query depends on a $@. | Mysql2.rb:3:12:3:17 | call to params | user-provided value | -| Mysql2.rb:17:27:17:75 | "SELECT * FROM users WHERE use..." | Mysql2.rb:3:12:3:17 | call to params | Mysql2.rb:17:27:17:75 | "SELECT * FROM users WHERE use..." | This SQL query depends on a $@. | Mysql2.rb:3:12:3:17 | call to params | user-provided value | | Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." | Mysql2.rb:3:12:3:17 | call to params | Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." | This SQL query depends on a $@. | Mysql2.rb:3:12:3:17 | call to params | user-provided value | edges | Mysql2.rb:3:5:3:8 | name | Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." | provenance | AdditionalTaintStep | -| Mysql2.rb:3:5:3:8 | name | Mysql2.rb:16:37:16:40 | name | provenance | | | Mysql2.rb:3:5:3:8 | name | Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." | provenance | AdditionalTaintStep | | Mysql2.rb:3:12:3:17 | call to params | Mysql2.rb:3:12:3:29 | ...[...] | provenance | | | Mysql2.rb:3:12:3:29 | ...[...] | Mysql2.rb:3:5:3:8 | name | provenance | | -| Mysql2.rb:16:5:16:11 | escaped | Mysql2.rb:17:27:17:75 | "SELECT * FROM users WHERE use..." | provenance | AdditionalTaintStep | -| Mysql2.rb:16:15:16:41 | call to escape | Mysql2.rb:16:5:16:11 | escaped | provenance | | -| Mysql2.rb:16:37:16:40 | name | Mysql2.rb:16:15:16:41 | call to escape | provenance | MaD:1 | -models -| 1 | Summary: Mysql2::Client!; Method[escape]; Argument[0]; ReturnValue; taint | nodes | Mysql2.rb:3:5:3:8 | name | semmle.label | name | | Mysql2.rb:3:12:3:17 | call to params | semmle.label | call to params | | Mysql2.rb:3:12:3:29 | ...[...] | semmle.label | ...[...] | | Mysql2.rb:13:27:13:72 | "SELECT * FROM users WHERE use..." | semmle.label | "SELECT * FROM users WHERE use..." | -| Mysql2.rb:16:5:16:11 | escaped | semmle.label | escaped | -| Mysql2.rb:16:15:16:41 | call to escape | semmle.label | call to escape | -| Mysql2.rb:16:37:16:40 | name | semmle.label | name | -| Mysql2.rb:17:27:17:75 | "SELECT * FROM users WHERE use..." | semmle.label | "SELECT * FROM users WHERE use..." | | Mysql2.rb:24:31:24:93 | "SELECT * FROM users WHERE use..." | semmle.label | "SELECT * FROM users WHERE use..." | subpaths -testFailures -| Mysql2.rb:17:27:17:75 | "SELECT * FROM users WHERE use..." | Unexpected result: Alert | From 1fa183ee2a73b886dea62c9c220c4082d1337a6f Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 17 Feb 2026 22:16:14 +0000 Subject: [PATCH 06/16] Improve Sqlite3 test --- .../frameworks/sqlite3/SqlInjection.expected | 12 +++++++++++ .../frameworks/sqlite3/SqlInjection.qlref | 4 ++++ .../frameworks/sqlite3/sqlite3.rb | 20 +++++++++++-------- 3 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.expected create mode 100644 ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.qlref diff --git a/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.expected b/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.expected new file mode 100644 index 000000000000..e094f9603c8d --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.expected @@ -0,0 +1,12 @@ +#select +| sqlite3.rb:29:16:29:67 | "select * from table where cat..." | sqlite3.rb:25:16:25:21 | call to params | sqlite3.rb:29:16:29:67 | "select * from table where cat..." | This SQL query depends on a $@. | sqlite3.rb:25:16:25:21 | call to params | user-provided value | +edges +| sqlite3.rb:25:5:25:12 | category | sqlite3.rb:29:16:29:67 | "select * from table where cat..." | provenance | AdditionalTaintStep | +| sqlite3.rb:25:16:25:21 | call to params | sqlite3.rb:25:16:25:32 | ...[...] | provenance | | +| sqlite3.rb:25:16:25:32 | ...[...] | sqlite3.rb:25:5:25:12 | category | provenance | | +nodes +| sqlite3.rb:25:5:25:12 | category | semmle.label | category | +| sqlite3.rb:25:16:25:21 | call to params | semmle.label | call to params | +| sqlite3.rb:25:16:25:32 | ...[...] | semmle.label | ...[...] | +| sqlite3.rb:29:16:29:67 | "select * from table where cat..." | semmle.label | "select * from table where cat..." | +subpaths diff --git a/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.qlref b/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.qlref new file mode 100644 index 000000000000..ff400a718e22 --- /dev/null +++ b/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.qlref @@ -0,0 +1,4 @@ +query: queries/security/cwe-089/SqlInjection.ql +postprocess: + - utils/test/PrettyPrintModels.ql + - utils/test/InlineExpectationsTestQuery.ql diff --git a/ruby/ql/test/library-tests/frameworks/sqlite3/sqlite3.rb b/ruby/ql/test/library-tests/frameworks/sqlite3/sqlite3.rb index 465bb708598d..36b146abe8d3 100644 --- a/ruby/ql/test/library-tests/frameworks/sqlite3/sqlite3.rb +++ b/ruby/ql/test/library-tests/frameworks/sqlite3/sqlite3.rb @@ -20,12 +20,16 @@ end -class MyDatabaseWrapper - def initialize(filename) - @db = SQLite3::Database.new(filename, results_as_hash: true) - end - - def select_rows(category) - @db.execute("select * from table") - end +class SqliteController < ActionController::Base + def sqlite3_handler + category = params[:category] # $ Source[rb/sql-injection] + db = SQLite3::Database.new "test.db" + + # BAD: SQL injection vulnerability + db.execute("select * from table where category = '#{category}'") # $ Alert[rb/sql-injection] + + # GOOD: Sanitized by SQLite3::Database.quote + sanitized_category = SQLite3::Database.quote(category) + db.execute("select * from table where category = '#{sanitized_category}'") + end end From 5df695bec93e39b3495da70aa826b92cccb9c5dd Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 17 Feb 2026 22:22:13 +0000 Subject: [PATCH 07/16] Move SQLite3 flow model to MaD and remove ql sanitizer --- .../codeql/ruby/frameworks/Sqlite3.model.yml | 6 +++++ .../ql/lib/codeql/ruby/frameworks/Sqlite3.qll | 22 ------------------- .../frameworks/sqlite3/SqlInjection.expected | 13 +++++++++++ 3 files changed, 19 insertions(+), 22 deletions(-) create mode 100644 ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.model.yml diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.model.yml b/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.model.yml new file mode 100644 index 000000000000..e6aeb2c82409 --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.model.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/ruby-all + extensible: summaryModel + data: + - ['SQLite3::Database!', 'Method[quote]', 'Argument[0]', 'ReturnValue', 'taint'] diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll b/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll index f3e7626f7337..1cf167baa72f 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.qll @@ -76,26 +76,4 @@ module Sqlite3 { override DataFlow::Node getSql() { result = this.getArgument(0) } } - - /** - * A call to `SQLite3::Database.quote`, considered as a sanitizer for SQL statements. - */ - private class SQLite3QuoteSanitization extends SqlSanitization { - SQLite3QuoteSanitization() { - this = API::getTopLevelMember("SQLite3").getMember("Database").getAMethodCall("quote") - } - } - - /** - * Flow summary for `SQLite3::Database.quote()`. - */ - private class QuoteSummary extends SummarizedCallable::Range { - QuoteSummary() { this = "SQLite3::Database.quote()" } - - override MethodCall getACall() { result = any(SQLite3QuoteSanitization c).asExpr().getExpr() } - - override predicate propagatesFlow(string input, string output, boolean preservesValue) { - input = "Argument[0]" and output = "ReturnValue" and preservesValue = false - } - } } diff --git a/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.expected b/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.expected index e094f9603c8d..47bbcc10c276 100644 --- a/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.expected +++ b/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.expected @@ -1,12 +1,25 @@ #select | sqlite3.rb:29:16:29:67 | "select * from table where cat..." | sqlite3.rb:25:16:25:21 | call to params | sqlite3.rb:29:16:29:67 | "select * from table where cat..." | This SQL query depends on a $@. | sqlite3.rb:25:16:25:21 | call to params | user-provided value | +| sqlite3.rb:33:16:33:77 | "select * from table where cat..." | sqlite3.rb:25:16:25:21 | call to params | sqlite3.rb:33:16:33:77 | "select * from table where cat..." | This SQL query depends on a $@. | sqlite3.rb:25:16:25:21 | call to params | user-provided value | edges | sqlite3.rb:25:5:25:12 | category | sqlite3.rb:29:16:29:67 | "select * from table where cat..." | provenance | AdditionalTaintStep | +| sqlite3.rb:25:5:25:12 | category | sqlite3.rb:32:50:32:57 | category | provenance | | | sqlite3.rb:25:16:25:21 | call to params | sqlite3.rb:25:16:25:32 | ...[...] | provenance | | | sqlite3.rb:25:16:25:32 | ...[...] | sqlite3.rb:25:5:25:12 | category | provenance | | +| sqlite3.rb:32:5:32:22 | sanitized_category | sqlite3.rb:33:16:33:77 | "select * from table where cat..." | provenance | AdditionalTaintStep | +| sqlite3.rb:32:26:32:58 | call to quote | sqlite3.rb:32:5:32:22 | sanitized_category | provenance | | +| sqlite3.rb:32:50:32:57 | category | sqlite3.rb:32:26:32:58 | call to quote | provenance | MaD:1 | +models +| 1 | Summary: SQLite3::Database!; Method[quote]; Argument[0]; ReturnValue; taint | nodes | sqlite3.rb:25:5:25:12 | category | semmle.label | category | | sqlite3.rb:25:16:25:21 | call to params | semmle.label | call to params | | sqlite3.rb:25:16:25:32 | ...[...] | semmle.label | ...[...] | | sqlite3.rb:29:16:29:67 | "select * from table where cat..." | semmle.label | "select * from table where cat..." | +| sqlite3.rb:32:5:32:22 | sanitized_category | semmle.label | sanitized_category | +| sqlite3.rb:32:26:32:58 | call to quote | semmle.label | call to quote | +| sqlite3.rb:32:50:32:57 | category | semmle.label | category | +| sqlite3.rb:33:16:33:77 | "select * from table where cat..." | semmle.label | "select * from table where cat..." | subpaths +testFailures +| sqlite3.rb:33:16:33:77 | "select * from table where cat..." | Unexpected result: Alert | From 4aee99f0ebe290273a32f2c0970c69cee7fe95a5 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 17 Feb 2026 22:23:18 +0000 Subject: [PATCH 08/16] Reinstate SQLite3 sanitizer in MaD --- .../ql/lib/codeql/ruby/frameworks/Sqlite3.model.yml | 5 +++++ .../frameworks/sqlite3/SqlInjection.expected | 13 ------------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.model.yml b/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.model.yml index e6aeb2c82409..13b7b5b48712 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.model.yml +++ b/ruby/ql/lib/codeql/ruby/frameworks/Sqlite3.model.yml @@ -4,3 +4,8 @@ extensions: extensible: summaryModel data: - ['SQLite3::Database!', 'Method[quote]', 'Argument[0]', 'ReturnValue', 'taint'] + - addsTo: + pack: codeql/ruby-all + extensible: barrierModel + data: + - ['SQLite3::Database!', 'Method[quote].ReturnValue', 'sql-injection'] diff --git a/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.expected b/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.expected index 47bbcc10c276..e094f9603c8d 100644 --- a/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.expected +++ b/ruby/ql/test/library-tests/frameworks/sqlite3/SqlInjection.expected @@ -1,25 +1,12 @@ #select | sqlite3.rb:29:16:29:67 | "select * from table where cat..." | sqlite3.rb:25:16:25:21 | call to params | sqlite3.rb:29:16:29:67 | "select * from table where cat..." | This SQL query depends on a $@. | sqlite3.rb:25:16:25:21 | call to params | user-provided value | -| sqlite3.rb:33:16:33:77 | "select * from table where cat..." | sqlite3.rb:25:16:25:21 | call to params | sqlite3.rb:33:16:33:77 | "select * from table where cat..." | This SQL query depends on a $@. | sqlite3.rb:25:16:25:21 | call to params | user-provided value | edges | sqlite3.rb:25:5:25:12 | category | sqlite3.rb:29:16:29:67 | "select * from table where cat..." | provenance | AdditionalTaintStep | -| sqlite3.rb:25:5:25:12 | category | sqlite3.rb:32:50:32:57 | category | provenance | | | sqlite3.rb:25:16:25:21 | call to params | sqlite3.rb:25:16:25:32 | ...[...] | provenance | | | sqlite3.rb:25:16:25:32 | ...[...] | sqlite3.rb:25:5:25:12 | category | provenance | | -| sqlite3.rb:32:5:32:22 | sanitized_category | sqlite3.rb:33:16:33:77 | "select * from table where cat..." | provenance | AdditionalTaintStep | -| sqlite3.rb:32:26:32:58 | call to quote | sqlite3.rb:32:5:32:22 | sanitized_category | provenance | | -| sqlite3.rb:32:50:32:57 | category | sqlite3.rb:32:26:32:58 | call to quote | provenance | MaD:1 | -models -| 1 | Summary: SQLite3::Database!; Method[quote]; Argument[0]; ReturnValue; taint | nodes | sqlite3.rb:25:5:25:12 | category | semmle.label | category | | sqlite3.rb:25:16:25:21 | call to params | semmle.label | call to params | | sqlite3.rb:25:16:25:32 | ...[...] | semmle.label | ...[...] | | sqlite3.rb:29:16:29:67 | "select * from table where cat..." | semmle.label | "select * from table where cat..." | -| sqlite3.rb:32:5:32:22 | sanitized_category | semmle.label | sanitized_category | -| sqlite3.rb:32:26:32:58 | call to quote | semmle.label | call to quote | -| sqlite3.rb:32:50:32:57 | category | semmle.label | category | -| sqlite3.rb:33:16:33:77 | "select * from table where cat..." | semmle.label | "select * from table where cat..." | subpaths -testFailures -| sqlite3.rb:33:16:33:77 | "select * from table where cat..." | Unexpected result: Alert | From 6294c3b3b8de70109da4edd0b27a836f277690f2 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 17 Feb 2026 12:39:07 +0000 Subject: [PATCH 09/16] Remove Shellwords sanitizer in ql Note that some sanitizers had no effect because flow through those functions wasn't modeled. --- .../ruby/security/CommandInjectionCustomizations.qll | 12 ------------ .../CommandInjection/CommandInjection.expected | 9 +++++++++ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll index d9551177875c..b1001b372e2a 100644 --- a/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll @@ -42,18 +42,6 @@ module CommandInjection { SystemCommandExecutionSink() { exists(SystemCommandExecution c | c.isShellInterpreted(this)) } } - /** - * A call to `Shellwords.escape` or `Shellwords.shellescape` sanitizes its input. - */ - class ShellwordsEscapeAsSanitizer extends Sanitizer { - ShellwordsEscapeAsSanitizer() { - this = API::getTopLevelMember("Shellwords").getAMethodCall(["escape", "shellescape"]) - or - // The method is also added as `String#shellescape`. - this.(DataFlow::CallNode).getMethodName() = "shellescape" - } - } - private class ExternalCommandInjectionSink extends Sink { ExternalCommandInjectionSink() { ModelOutput::sinkNode(this, "command-injection") } } diff --git a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected index 2173fed576a2..34d75aa6431a 100644 --- a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected +++ b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected @@ -13,6 +13,7 @@ | CommandInjection.rb:83:14:83:34 | "echo #{...}" | CommandInjection.rb:82:23:82:33 | blah_number | CommandInjection.rb:83:14:83:34 | "echo #{...}" | This command depends on a $@. | CommandInjection.rb:82:23:82:33 | blah_number | user-provided value | | CommandInjection.rb:92:14:92:39 | "echo #{...}" | CommandInjection.rb:92:22:92:37 | ...[...] | CommandInjection.rb:92:14:92:39 | "echo #{...}" | This command depends on a $@. | CommandInjection.rb:92:22:92:37 | ...[...] | user-provided value | | CommandInjection.rb:105:16:105:28 | "cat #{...}" | CommandInjection.rb:104:16:104:21 | call to params | CommandInjection.rb:105:16:105:28 | "cat #{...}" | This command depends on a $@. | CommandInjection.rb:104:16:104:21 | call to params | user-provided value | +| CommandInjection.rb:107:16:107:40 | "cat #{...}" | CommandInjection.rb:104:16:104:21 | call to params | CommandInjection.rb:107:16:107:40 | "cat #{...}" | This command depends on a $@. | CommandInjection.rb:104:16:104:21 | call to params | user-provided value | | CommandInjection.rb:112:33:112:44 | ...[...] | CommandInjection.rb:112:33:112:38 | call to params | CommandInjection.rb:112:33:112:44 | ...[...] | This command depends on a $@. | CommandInjection.rb:112:33:112:38 | call to params | user-provided value | | CommandInjection.rb:114:41:114:56 | "#{...}" | CommandInjection.rb:114:44:114:49 | call to params | CommandInjection.rb:114:41:114:56 | "#{...}" | This command depends on a $@. | CommandInjection.rb:114:44:114:49 | call to params | user-provided value | edges @@ -36,8 +37,11 @@ edges | CommandInjection.rb:82:23:82:33 | blah_number | CommandInjection.rb:83:14:83:34 | "echo #{...}" | provenance | AdditionalTaintStep | | CommandInjection.rb:92:22:92:37 | ...[...] | CommandInjection.rb:92:14:92:39 | "echo #{...}" | provenance | AdditionalTaintStep | | CommandInjection.rb:104:9:104:12 | file | CommandInjection.rb:105:16:105:28 | "cat #{...}" | provenance | AdditionalTaintStep | +| CommandInjection.rb:104:9:104:12 | file | CommandInjection.rb:107:23:107:26 | file | provenance | | | CommandInjection.rb:104:16:104:21 | call to params | CommandInjection.rb:104:16:104:28 | ...[...] | provenance | | | CommandInjection.rb:104:16:104:28 | ...[...] | CommandInjection.rb:104:9:104:12 | file | provenance | | +| CommandInjection.rb:107:23:107:26 | file | CommandInjection.rb:107:23:107:38 | call to shellescape | provenance | | +| CommandInjection.rb:107:23:107:38 | call to shellescape | CommandInjection.rb:107:16:107:40 | "cat #{...}" | provenance | AdditionalTaintStep | | CommandInjection.rb:112:33:112:38 | call to params | CommandInjection.rb:112:33:112:44 | ...[...] | provenance | Sink:MaD:1 | | CommandInjection.rb:114:44:114:49 | call to params | CommandInjection.rb:114:44:114:54 | ...[...] | provenance | | | CommandInjection.rb:114:44:114:54 | ...[...] | CommandInjection.rb:114:41:114:56 | "#{...}" | provenance | AdditionalTaintStep Sink:MaD:2 | @@ -74,9 +78,14 @@ nodes | CommandInjection.rb:104:16:104:21 | call to params | semmle.label | call to params | | CommandInjection.rb:104:16:104:28 | ...[...] | semmle.label | ...[...] | | CommandInjection.rb:105:16:105:28 | "cat #{...}" | semmle.label | "cat #{...}" | +| CommandInjection.rb:107:16:107:40 | "cat #{...}" | semmle.label | "cat #{...}" | +| CommandInjection.rb:107:23:107:26 | file | semmle.label | file | +| CommandInjection.rb:107:23:107:38 | call to shellescape | semmle.label | call to shellescape | | CommandInjection.rb:112:33:112:38 | call to params | semmle.label | call to params | | CommandInjection.rb:112:33:112:44 | ...[...] | semmle.label | ...[...] | | CommandInjection.rb:114:41:114:56 | "#{...}" | semmle.label | "#{...}" | | CommandInjection.rb:114:44:114:49 | call to params | semmle.label | call to params | | CommandInjection.rb:114:44:114:54 | ...[...] | semmle.label | ...[...] | subpaths +testFailures +| CommandInjection.rb:107:16:107:40 | "cat #{...}" | Unexpected result: Alert | From b3681f7a0c3128fb1dbb1d1e9f9814c43a4def64 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 17 Feb 2026 12:39:54 +0000 Subject: [PATCH 10/16] Model flow through Shellwords escape and shellescape --- .../frameworks/stdlib/Shellwords.model.yml | 6 ++++++ .../CommandInjection.expected | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 ruby/ql/lib/codeql/ruby/frameworks/stdlib/Shellwords.model.yml diff --git a/ruby/ql/lib/codeql/ruby/frameworks/stdlib/Shellwords.model.yml b/ruby/ql/lib/codeql/ruby/frameworks/stdlib/Shellwords.model.yml new file mode 100644 index 000000000000..ec3f0430dbaf --- /dev/null +++ b/ruby/ql/lib/codeql/ruby/frameworks/stdlib/Shellwords.model.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/ruby-all + extensible: summaryModel + data: + - ['Shellwords!', 'Method[escape,shellescape]', 'Argument[0]', 'ReturnValue', 'taint'] diff --git a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected index 34d75aa6431a..59881ddff97e 100644 --- a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected +++ b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected @@ -4,6 +4,8 @@ | CommandInjection.rb:10:14:10:16 | cmd | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:10:14:10:16 | cmd | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | | CommandInjection.rb:11:17:11:22 | #{...} | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:11:17:11:22 | #{...} | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | | CommandInjection.rb:13:9:13:14 | #{...} | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:13:9:13:14 | #{...} | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | +| CommandInjection.rb:18:15:18:27 | #{...} | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:18:15:18:27 | #{...} | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | +| CommandInjection.rb:21:15:21:27 | #{...} | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:21:15:21:27 | #{...} | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | | CommandInjection.rb:30:19:30:24 | #{...} | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:30:19:30:24 | #{...} | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | | CommandInjection.rb:34:24:34:36 | "echo #{...}" | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:34:24:34:36 | "echo #{...}" | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | | CommandInjection.rb:35:39:35:51 | "grep #{...}" | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:35:39:35:51 | "grep #{...}" | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | @@ -22,11 +24,19 @@ edges | CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:10:14:10:16 | cmd | provenance | | | CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:11:17:11:22 | #{...} | provenance | | | CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:13:9:13:14 | #{...} | provenance | | +| CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:17:40:17:42 | cmd | provenance | | +| CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:20:45:20:47 | cmd | provenance | | | CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:30:19:30:24 | #{...} | provenance | | | CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:34:24:34:36 | "echo #{...}" | provenance | AdditionalTaintStep | | CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:35:39:35:51 | "grep #{...}" | provenance | AdditionalTaintStep | | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:6:15:6:26 | ...[...] | provenance | | | CommandInjection.rb:6:15:6:26 | ...[...] | CommandInjection.rb:6:9:6:11 | cmd | provenance | | +| CommandInjection.rb:17:9:17:18 | safe_cmd_1 | CommandInjection.rb:18:15:18:27 | #{...} | provenance | | +| CommandInjection.rb:17:22:17:43 | call to escape | CommandInjection.rb:17:9:17:18 | safe_cmd_1 | provenance | | +| CommandInjection.rb:17:40:17:42 | cmd | CommandInjection.rb:17:22:17:43 | call to escape | provenance | MaD:3 | +| CommandInjection.rb:20:9:20:18 | safe_cmd_2 | CommandInjection.rb:21:15:21:27 | #{...} | provenance | | +| CommandInjection.rb:20:22:20:48 | call to shellescape | CommandInjection.rb:20:9:20:18 | safe_cmd_2 | provenance | | +| CommandInjection.rb:20:45:20:47 | cmd | CommandInjection.rb:20:22:20:48 | call to shellescape | provenance | MaD:3 | | CommandInjection.rb:47:9:47:11 | cmd | CommandInjection.rb:51:24:51:36 | "echo #{...}" | provenance | AdditionalTaintStep | | CommandInjection.rb:47:15:47:20 | call to params | CommandInjection.rb:47:15:47:26 | ...[...] | provenance | | | CommandInjection.rb:47:15:47:26 | ...[...] | CommandInjection.rb:47:9:47:11 | cmd | provenance | | @@ -48,6 +58,7 @@ edges models | 1 | Sink: Terrapin::CommandLine!; Method[new].Argument[0]; command-injection | | 2 | Sink: Terrapin::CommandLine!; Method[new].Argument[1]; command-injection | +| 3 | Summary: Shellwords!; Method[escape,shellescape]; Argument[0]; ReturnValue; taint | nodes | CommandInjection.rb:6:9:6:11 | cmd | semmle.label | cmd | | CommandInjection.rb:6:15:6:20 | call to params | semmle.label | call to params | @@ -57,6 +68,14 @@ nodes | CommandInjection.rb:10:14:10:16 | cmd | semmle.label | cmd | | CommandInjection.rb:11:17:11:22 | #{...} | semmle.label | #{...} | | CommandInjection.rb:13:9:13:14 | #{...} | semmle.label | #{...} | +| CommandInjection.rb:17:9:17:18 | safe_cmd_1 | semmle.label | safe_cmd_1 | +| CommandInjection.rb:17:22:17:43 | call to escape | semmle.label | call to escape | +| CommandInjection.rb:17:40:17:42 | cmd | semmle.label | cmd | +| CommandInjection.rb:18:15:18:27 | #{...} | semmle.label | #{...} | +| CommandInjection.rb:20:9:20:18 | safe_cmd_2 | semmle.label | safe_cmd_2 | +| CommandInjection.rb:20:22:20:48 | call to shellescape | semmle.label | call to shellescape | +| CommandInjection.rb:20:45:20:47 | cmd | semmle.label | cmd | +| CommandInjection.rb:21:15:21:27 | #{...} | semmle.label | #{...} | | CommandInjection.rb:30:19:30:24 | #{...} | semmle.label | #{...} | | CommandInjection.rb:34:24:34:36 | "echo #{...}" | semmle.label | "echo #{...}" | | CommandInjection.rb:35:39:35:51 | "grep #{...}" | semmle.label | "grep #{...}" | @@ -88,4 +107,6 @@ nodes | CommandInjection.rb:114:44:114:54 | ...[...] | semmle.label | ...[...] | subpaths testFailures +| CommandInjection.rb:18:15:18:27 | #{...} | Unexpected result: Alert | +| CommandInjection.rb:21:15:21:27 | #{...} | Unexpected result: Alert | | CommandInjection.rb:107:16:107:40 | "cat #{...}" | Unexpected result: Alert | From de5470a85c6342b97d1e0e2041f686de8dad17ff Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 17 Feb 2026 12:41:28 +0000 Subject: [PATCH 11/16] Add MaD barriers for Shellwords.escape and shellescape Note that this will only block flow for queries that use the kind `command-injection`. --- .../frameworks/stdlib/Shellwords.model.yml | 6 ++++++ .../CommandInjection.expected | 21 ------------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/frameworks/stdlib/Shellwords.model.yml b/ruby/ql/lib/codeql/ruby/frameworks/stdlib/Shellwords.model.yml index ec3f0430dbaf..283b1daa8fa3 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/stdlib/Shellwords.model.yml +++ b/ruby/ql/lib/codeql/ruby/frameworks/stdlib/Shellwords.model.yml @@ -4,3 +4,9 @@ extensions: extensible: summaryModel data: - ['Shellwords!', 'Method[escape,shellescape]', 'Argument[0]', 'ReturnValue', 'taint'] + + - addsTo: + pack: codeql/ruby-all + extensible: barrierModel + data: + - ['Shellwords!', 'Method[escape,shellescape].ReturnValue', 'command-injection'] diff --git a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected index 59881ddff97e..34d75aa6431a 100644 --- a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected +++ b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected @@ -4,8 +4,6 @@ | CommandInjection.rb:10:14:10:16 | cmd | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:10:14:10:16 | cmd | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | | CommandInjection.rb:11:17:11:22 | #{...} | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:11:17:11:22 | #{...} | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | | CommandInjection.rb:13:9:13:14 | #{...} | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:13:9:13:14 | #{...} | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | -| CommandInjection.rb:18:15:18:27 | #{...} | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:18:15:18:27 | #{...} | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | -| CommandInjection.rb:21:15:21:27 | #{...} | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:21:15:21:27 | #{...} | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | | CommandInjection.rb:30:19:30:24 | #{...} | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:30:19:30:24 | #{...} | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | | CommandInjection.rb:34:24:34:36 | "echo #{...}" | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:34:24:34:36 | "echo #{...}" | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | | CommandInjection.rb:35:39:35:51 | "grep #{...}" | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:35:39:35:51 | "grep #{...}" | This command depends on a $@. | CommandInjection.rb:6:15:6:20 | call to params | user-provided value | @@ -24,19 +22,11 @@ edges | CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:10:14:10:16 | cmd | provenance | | | CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:11:17:11:22 | #{...} | provenance | | | CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:13:9:13:14 | #{...} | provenance | | -| CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:17:40:17:42 | cmd | provenance | | -| CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:20:45:20:47 | cmd | provenance | | | CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:30:19:30:24 | #{...} | provenance | | | CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:34:24:34:36 | "echo #{...}" | provenance | AdditionalTaintStep | | CommandInjection.rb:6:9:6:11 | cmd | CommandInjection.rb:35:39:35:51 | "grep #{...}" | provenance | AdditionalTaintStep | | CommandInjection.rb:6:15:6:20 | call to params | CommandInjection.rb:6:15:6:26 | ...[...] | provenance | | | CommandInjection.rb:6:15:6:26 | ...[...] | CommandInjection.rb:6:9:6:11 | cmd | provenance | | -| CommandInjection.rb:17:9:17:18 | safe_cmd_1 | CommandInjection.rb:18:15:18:27 | #{...} | provenance | | -| CommandInjection.rb:17:22:17:43 | call to escape | CommandInjection.rb:17:9:17:18 | safe_cmd_1 | provenance | | -| CommandInjection.rb:17:40:17:42 | cmd | CommandInjection.rb:17:22:17:43 | call to escape | provenance | MaD:3 | -| CommandInjection.rb:20:9:20:18 | safe_cmd_2 | CommandInjection.rb:21:15:21:27 | #{...} | provenance | | -| CommandInjection.rb:20:22:20:48 | call to shellescape | CommandInjection.rb:20:9:20:18 | safe_cmd_2 | provenance | | -| CommandInjection.rb:20:45:20:47 | cmd | CommandInjection.rb:20:22:20:48 | call to shellescape | provenance | MaD:3 | | CommandInjection.rb:47:9:47:11 | cmd | CommandInjection.rb:51:24:51:36 | "echo #{...}" | provenance | AdditionalTaintStep | | CommandInjection.rb:47:15:47:20 | call to params | CommandInjection.rb:47:15:47:26 | ...[...] | provenance | | | CommandInjection.rb:47:15:47:26 | ...[...] | CommandInjection.rb:47:9:47:11 | cmd | provenance | | @@ -58,7 +48,6 @@ edges models | 1 | Sink: Terrapin::CommandLine!; Method[new].Argument[0]; command-injection | | 2 | Sink: Terrapin::CommandLine!; Method[new].Argument[1]; command-injection | -| 3 | Summary: Shellwords!; Method[escape,shellescape]; Argument[0]; ReturnValue; taint | nodes | CommandInjection.rb:6:9:6:11 | cmd | semmle.label | cmd | | CommandInjection.rb:6:15:6:20 | call to params | semmle.label | call to params | @@ -68,14 +57,6 @@ nodes | CommandInjection.rb:10:14:10:16 | cmd | semmle.label | cmd | | CommandInjection.rb:11:17:11:22 | #{...} | semmle.label | #{...} | | CommandInjection.rb:13:9:13:14 | #{...} | semmle.label | #{...} | -| CommandInjection.rb:17:9:17:18 | safe_cmd_1 | semmle.label | safe_cmd_1 | -| CommandInjection.rb:17:22:17:43 | call to escape | semmle.label | call to escape | -| CommandInjection.rb:17:40:17:42 | cmd | semmle.label | cmd | -| CommandInjection.rb:18:15:18:27 | #{...} | semmle.label | #{...} | -| CommandInjection.rb:20:9:20:18 | safe_cmd_2 | semmle.label | safe_cmd_2 | -| CommandInjection.rb:20:22:20:48 | call to shellescape | semmle.label | call to shellescape | -| CommandInjection.rb:20:45:20:47 | cmd | semmle.label | cmd | -| CommandInjection.rb:21:15:21:27 | #{...} | semmle.label | #{...} | | CommandInjection.rb:30:19:30:24 | #{...} | semmle.label | #{...} | | CommandInjection.rb:34:24:34:36 | "echo #{...}" | semmle.label | "echo #{...}" | | CommandInjection.rb:35:39:35:51 | "grep #{...}" | semmle.label | "grep #{...}" | @@ -107,6 +88,4 @@ nodes | CommandInjection.rb:114:44:114:54 | ...[...] | semmle.label | ...[...] | subpaths testFailures -| CommandInjection.rb:18:15:18:27 | #{...} | Unexpected result: Alert | -| CommandInjection.rb:21:15:21:27 | #{...} | Unexpected result: Alert | | CommandInjection.rb:107:16:107:40 | "cat #{...}" | Unexpected result: Alert | From eb7f1989c72149ebea41f242ee8998ff1174970f Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 17 Feb 2026 12:43:22 +0000 Subject: [PATCH 12/16] Reinstate ql model for `String#shellescape` --- .../ruby/security/CommandInjectionCustomizations.qll | 10 ++++++++++ .../cwe-078/CommandInjection/CommandInjection.expected | 9 --------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll b/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll index b1001b372e2a..479907d2052d 100644 --- a/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll +++ b/ruby/ql/lib/codeql/ruby/security/CommandInjectionCustomizations.qll @@ -42,6 +42,16 @@ module CommandInjection { SystemCommandExecutionSink() { exists(SystemCommandExecution c | c.isShellInterpreted(this)) } } + /** + * A call to `String#shellescape` sanitizes its input. + */ + class ShellwordsEscapeAsSanitizer extends Sanitizer { + ShellwordsEscapeAsSanitizer() { + // The `Shellwords.shellescape` method is also added as `String#shellescape`. + this.(DataFlow::CallNode).getMethodName() = "shellescape" + } + } + private class ExternalCommandInjectionSink extends Sink { ExternalCommandInjectionSink() { ModelOutput::sinkNode(this, "command-injection") } } diff --git a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected index 34d75aa6431a..2173fed576a2 100644 --- a/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected +++ b/ruby/ql/test/query-tests/security/cwe-078/CommandInjection/CommandInjection.expected @@ -13,7 +13,6 @@ | CommandInjection.rb:83:14:83:34 | "echo #{...}" | CommandInjection.rb:82:23:82:33 | blah_number | CommandInjection.rb:83:14:83:34 | "echo #{...}" | This command depends on a $@. | CommandInjection.rb:82:23:82:33 | blah_number | user-provided value | | CommandInjection.rb:92:14:92:39 | "echo #{...}" | CommandInjection.rb:92:22:92:37 | ...[...] | CommandInjection.rb:92:14:92:39 | "echo #{...}" | This command depends on a $@. | CommandInjection.rb:92:22:92:37 | ...[...] | user-provided value | | CommandInjection.rb:105:16:105:28 | "cat #{...}" | CommandInjection.rb:104:16:104:21 | call to params | CommandInjection.rb:105:16:105:28 | "cat #{...}" | This command depends on a $@. | CommandInjection.rb:104:16:104:21 | call to params | user-provided value | -| CommandInjection.rb:107:16:107:40 | "cat #{...}" | CommandInjection.rb:104:16:104:21 | call to params | CommandInjection.rb:107:16:107:40 | "cat #{...}" | This command depends on a $@. | CommandInjection.rb:104:16:104:21 | call to params | user-provided value | | CommandInjection.rb:112:33:112:44 | ...[...] | CommandInjection.rb:112:33:112:38 | call to params | CommandInjection.rb:112:33:112:44 | ...[...] | This command depends on a $@. | CommandInjection.rb:112:33:112:38 | call to params | user-provided value | | CommandInjection.rb:114:41:114:56 | "#{...}" | CommandInjection.rb:114:44:114:49 | call to params | CommandInjection.rb:114:41:114:56 | "#{...}" | This command depends on a $@. | CommandInjection.rb:114:44:114:49 | call to params | user-provided value | edges @@ -37,11 +36,8 @@ edges | CommandInjection.rb:82:23:82:33 | blah_number | CommandInjection.rb:83:14:83:34 | "echo #{...}" | provenance | AdditionalTaintStep | | CommandInjection.rb:92:22:92:37 | ...[...] | CommandInjection.rb:92:14:92:39 | "echo #{...}" | provenance | AdditionalTaintStep | | CommandInjection.rb:104:9:104:12 | file | CommandInjection.rb:105:16:105:28 | "cat #{...}" | provenance | AdditionalTaintStep | -| CommandInjection.rb:104:9:104:12 | file | CommandInjection.rb:107:23:107:26 | file | provenance | | | CommandInjection.rb:104:16:104:21 | call to params | CommandInjection.rb:104:16:104:28 | ...[...] | provenance | | | CommandInjection.rb:104:16:104:28 | ...[...] | CommandInjection.rb:104:9:104:12 | file | provenance | | -| CommandInjection.rb:107:23:107:26 | file | CommandInjection.rb:107:23:107:38 | call to shellescape | provenance | | -| CommandInjection.rb:107:23:107:38 | call to shellescape | CommandInjection.rb:107:16:107:40 | "cat #{...}" | provenance | AdditionalTaintStep | | CommandInjection.rb:112:33:112:38 | call to params | CommandInjection.rb:112:33:112:44 | ...[...] | provenance | Sink:MaD:1 | | CommandInjection.rb:114:44:114:49 | call to params | CommandInjection.rb:114:44:114:54 | ...[...] | provenance | | | CommandInjection.rb:114:44:114:54 | ...[...] | CommandInjection.rb:114:41:114:56 | "#{...}" | provenance | AdditionalTaintStep Sink:MaD:2 | @@ -78,14 +74,9 @@ nodes | CommandInjection.rb:104:16:104:21 | call to params | semmle.label | call to params | | CommandInjection.rb:104:16:104:28 | ...[...] | semmle.label | ...[...] | | CommandInjection.rb:105:16:105:28 | "cat #{...}" | semmle.label | "cat #{...}" | -| CommandInjection.rb:107:16:107:40 | "cat #{...}" | semmle.label | "cat #{...}" | -| CommandInjection.rb:107:23:107:26 | file | semmle.label | file | -| CommandInjection.rb:107:23:107:38 | call to shellescape | semmle.label | call to shellescape | | CommandInjection.rb:112:33:112:38 | call to params | semmle.label | call to params | | CommandInjection.rb:112:33:112:44 | ...[...] | semmle.label | ...[...] | | CommandInjection.rb:114:41:114:56 | "#{...}" | semmle.label | "#{...}" | | CommandInjection.rb:114:44:114:49 | call to params | semmle.label | call to params | | CommandInjection.rb:114:44:114:54 | ...[...] | semmle.label | ...[...] | subpaths -testFailures -| CommandInjection.rb:107:16:107:40 | "cat #{...}" | Unexpected result: Alert | From 1bff7a3eb8cc6ccb7ab6d79023efc6e58637c085 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 17 Feb 2026 22:29:35 +0000 Subject: [PATCH 13/16] Add change note --- .../2026-02-17-flow-through-shellwords-escape-shellescape.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 ruby/ql/lib/change-notes/2026-02-17-flow-through-shellwords-escape-shellescape.md diff --git a/ruby/ql/lib/change-notes/2026-02-17-flow-through-shellwords-escape-shellescape.md b/ruby/ql/lib/change-notes/2026-02-17-flow-through-shellwords-escape-shellescape.md new file mode 100644 index 000000000000..43042f553f69 --- /dev/null +++ b/ruby/ql/lib/change-notes/2026-02-17-flow-through-shellwords-escape-shellescape.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* We now track taint flow through `Shellwords.escape` and `Shellwords.shellescape` for all queries except command injection, for which they are sanitizers. From f577e973bc6c52bc06629e29ae235dc630935ef6 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 18 Feb 2026 13:39:06 +0000 Subject: [PATCH 14/16] Update other test in same folder --- .../test/library-tests/frameworks/sqlite3/Sqlite3.expected | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ruby/ql/test/library-tests/frameworks/sqlite3/Sqlite3.expected b/ruby/ql/test/library-tests/frameworks/sqlite3/Sqlite3.expected index 9e7263aa3bb8..b3573ed217ea 100644 --- a/ruby/ql/test/library-tests/frameworks/sqlite3/Sqlite3.expected +++ b/ruby/ql/test/library-tests/frameworks/sqlite3/Sqlite3.expected @@ -2,9 +2,11 @@ sqlite3SqlConstruction | sqlite3.rb:5:1:5:17 | call to execute | sqlite3.rb:5:12:5:17 | <<-SQL | | sqlite3.rb:12:8:12:41 | call to prepare | sqlite3.rb:12:19:12:41 | "select * from numbers" | | sqlite3.rb:17:3:19:5 | call to execute | sqlite3.rb:17:15:17:35 | "select * from table" | -| sqlite3.rb:29:7:29:40 | call to execute | sqlite3.rb:29:19:29:39 | "select * from table" | +| sqlite3.rb:29:5:29:68 | call to execute | sqlite3.rb:29:16:29:67 | "select * from table where cat..." | +| sqlite3.rb:33:5:33:78 | call to execute | sqlite3.rb:33:16:33:77 | "select * from table where cat..." | sqlite3SqlExecution | sqlite3.rb:5:1:5:17 | call to execute | sqlite3.rb:5:12:5:17 | <<-SQL | | sqlite3.rb:14:1:14:12 | call to execute | sqlite3.rb:12:19:12:41 | "select * from numbers" | | sqlite3.rb:17:3:19:5 | call to execute | sqlite3.rb:17:15:17:35 | "select * from table" | -| sqlite3.rb:29:7:29:40 | call to execute | sqlite3.rb:29:19:29:39 | "select * from table" | +| sqlite3.rb:29:5:29:68 | call to execute | sqlite3.rb:29:16:29:67 | "select * from table where cat..." | +| sqlite3.rb:33:5:33:78 | call to execute | sqlite3.rb:33:16:33:77 | "select * from table where cat..." | From 05d681fe1955e4badedab6a9575262e422fa48d8 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 18 Feb 2026 13:44:01 +0000 Subject: [PATCH 15/16] Update taintstep test for models becoming MaD --- ruby/ql/test/library-tests/dataflow/local/TaintStep.expected | 2 -- 1 file changed, 2 deletions(-) diff --git a/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected b/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected index 4fa46d163b4c..353edd203f95 100644 --- a/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected +++ b/ruby/ql/test/library-tests/dataflow/local/TaintStep.expected @@ -2921,13 +2921,11 @@ | file://:0:0:0:0 | [summary param] position 0 in File.realdirpath | file://:0:0:0:0 | [summary] to write: ReturnValue in File.realdirpath | | file://:0:0:0:0 | [summary param] position 0 in File.realpath | file://:0:0:0:0 | [summary] to write: ReturnValue in File.realpath | | file://:0:0:0:0 | [summary param] position 0 in Hash[] | file://:0:0:0:0 | [summary] read: Argument[0].Element[any] in Hash[] | -| file://:0:0:0:0 | [summary param] position 0 in Mysql2::Client.escape() | file://:0:0:0:0 | [summary] to write: ReturnValue in Mysql2::Client.escape() | | file://:0:0:0:0 | [summary param] position 0 in Mysql2::Client.new() | file://:0:0:0:0 | [summary] to write: ReturnValue in Mysql2::Client.new() | | file://:0:0:0:0 | [summary param] position 0 in Net::LDAP.new | file://:0:0:0:0 | [summary] to write: ReturnValue in Net::LDAP.new | | file://:0:0:0:0 | [summary param] position 0 in Net::LDAP::Filter | file://:0:0:0:0 | [summary] to write: ReturnValue in Net::LDAP::Filter | | file://:0:0:0:0 | [summary param] position 0 in PG.new() | file://:0:0:0:0 | [summary] to write: ReturnValue in PG.new() | | file://:0:0:0:0 | [summary param] position 0 in Rack::Utils.parse_query | file://:0:0:0:0 | [summary] to write: ReturnValue in Rack::Utils.parse_query | -| file://:0:0:0:0 | [summary param] position 0 in SQLite3::Database.quote() | file://:0:0:0:0 | [summary] to write: ReturnValue in SQLite3::Database.quote() | | file://:0:0:0:0 | [summary param] position 0 in Sequel.connect | file://:0:0:0:0 | [summary] to write: ReturnValue in Sequel.connect | | file://:0:0:0:0 | [summary param] position 0 in String.try_convert | file://:0:0:0:0 | [summary] to write: ReturnValue in String.try_convert | | file://:0:0:0:0 | [summary param] position 0 in \| | file://:0:0:0:0 | [summary] read: Argument[0].Element[any] in \| | From 1d6b8c5120ae49201b4043cef68e9b14b6cdee76 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 18 Feb 2026 13:41:33 +0000 Subject: [PATCH 16/16] Use postprocessing queries for unrelated test Need to do this because the model numbering was changing. At the same time we may as well use inline expectations. --- .../CodeInjection/CodeInjection.expected | 112 +++++++++--------- .../cwe-094/CodeInjection/CodeInjection.qlref | 5 +- .../cwe-094/CodeInjection/CodeInjection.rb | 42 ++++--- 3 files changed, 85 insertions(+), 74 deletions(-) diff --git a/ruby/ql/test/query-tests/security/cwe-094/CodeInjection/CodeInjection.expected b/ruby/ql/test/query-tests/security/cwe-094/CodeInjection/CodeInjection.expected index f598d37e32a5..07febf8cda63 100644 --- a/ruby/ql/test/query-tests/security/cwe-094/CodeInjection/CodeInjection.expected +++ b/ruby/ql/test/query-tests/security/cwe-094/CodeInjection/CodeInjection.expected @@ -1,3 +1,46 @@ +#select +| CodeInjection.rb:8:10:8:13 | code | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:8:10:8:13 | code | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | +| CodeInjection.rb:11:10:11:15 | call to params | CodeInjection.rb:11:10:11:15 | call to params | CodeInjection.rb:11:10:11:15 | call to params | This code execution depends on a $@. | CodeInjection.rb:11:10:11:15 | call to params | user-provided value | +| CodeInjection.rb:20:20:20:23 | code | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:20:20:20:23 | code | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | +| CodeInjection.rb:23:21:23:24 | code | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:23:21:23:24 | code | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | +| CodeInjection.rb:29:15:29:18 | code | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:29:15:29:18 | code | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | +| CodeInjection.rb:32:19:32:22 | code | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:32:19:32:22 | code | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | +| CodeInjection.rb:38:10:38:28 | call to escape | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:38:10:38:28 | call to escape | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | +| CodeInjection.rb:41:40:41:43 | code | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:41:40:41:43 | code | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | +| CodeInjection.rb:81:16:81:19 | code | CodeInjection.rb:78:12:78:17 | call to params | CodeInjection.rb:81:16:81:19 | code | This code execution depends on a $@. | CodeInjection.rb:78:12:78:17 | call to params | user-provided value | +| CodeInjection.rb:90:10:90:37 | ... + ... | CodeInjection.rb:78:12:78:17 | call to params | CodeInjection.rb:90:10:90:37 | ... + ... | This code execution depends on a $@. | CodeInjection.rb:78:12:78:17 | call to params | user-provided value | +| CodeInjection.rb:93:10:93:32 | "prefix_#{...}_suffix" | CodeInjection.rb:78:12:78:17 | call to params | CodeInjection.rb:93:10:93:32 | "prefix_#{...}_suffix" | This code execution depends on a $@. | CodeInjection.rb:78:12:78:17 | call to params | user-provided value | +| CodeInjection.rb:96:10:96:13 | code | CodeInjection.rb:78:12:78:17 | call to params | CodeInjection.rb:96:10:96:13 | code | This code execution depends on a $@. | CodeInjection.rb:78:12:78:17 | call to params | user-provided value | +| CodeInjection.rb:118:10:118:13 | @foo | CodeInjection.rb:111:12:111:17 | call to params | CodeInjection.rb:118:10:118:13 | @foo | This code execution depends on a $@. | CodeInjection.rb:111:12:111:17 | call to params | user-provided value | +edges +| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:8:10:8:13 | code | provenance | | +| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:20:20:20:23 | code | provenance | | +| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:23:21:23:24 | code | provenance | | +| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:29:15:29:18 | code | provenance | | +| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:32:19:32:22 | code | provenance | | +| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:38:24:38:27 | code | provenance | | +| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:41:40:41:43 | code | provenance | | +| CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:5:12:5:24 | ...[...] | provenance | | +| CodeInjection.rb:5:12:5:24 | ...[...] | CodeInjection.rb:5:5:5:8 | code | provenance | | +| CodeInjection.rb:38:24:38:27 | code | CodeInjection.rb:38:10:38:28 | call to escape | provenance | MaD:1 | +| CodeInjection.rb:78:5:78:8 | code | CodeInjection.rb:81:16:81:19 | code | provenance | | +| CodeInjection.rb:78:5:78:8 | code | CodeInjection.rb:90:10:90:37 | ... + ... | provenance | | +| CodeInjection.rb:78:5:78:8 | code | CodeInjection.rb:90:22:90:25 | code | provenance | | +| CodeInjection.rb:78:5:78:8 | code | CodeInjection.rb:93:10:93:32 | "prefix_#{...}_suffix" | provenance | AdditionalTaintStep | +| CodeInjection.rb:78:5:78:8 | code | CodeInjection.rb:96:10:96:13 | code | provenance | | +| CodeInjection.rb:78:12:78:17 | call to params | CodeInjection.rb:78:12:78:24 | ...[...] | provenance | | +| CodeInjection.rb:78:12:78:24 | ...[...] | CodeInjection.rb:78:5:78:8 | code | provenance | | +| CodeInjection.rb:90:10:90:25 | ... + ... : [collection] [element] | CodeInjection.rb:90:10:90:37 | ... + ... | provenance | | +| CodeInjection.rb:90:22:90:25 | code | CodeInjection.rb:90:10:90:25 | ... + ... : [collection] [element] | provenance | | +| CodeInjection.rb:107:3:108:5 | self in index : PostsController [@foo] | CodeInjection.rb:117:3:119:5 | self in baz : PostsController [@foo] | provenance | | +| CodeInjection.rb:111:5:111:8 | [post] self [@foo] | CodeInjection.rb:114:3:115:5 | self in bar : PostsController [@foo] | provenance | | +| CodeInjection.rb:111:12:111:17 | call to params | CodeInjection.rb:111:12:111:23 | ...[...] | provenance | | +| CodeInjection.rb:111:12:111:23 | ...[...] | CodeInjection.rb:111:5:111:8 | [post] self [@foo] | provenance | | +| CodeInjection.rb:114:3:115:5 | self in bar : PostsController [@foo] | CodeInjection.rb:107:3:108:5 | self in index : PostsController [@foo] | provenance | | +| CodeInjection.rb:117:3:119:5 | self in baz : PostsController [@foo] | CodeInjection.rb:118:10:118:13 | self : PostsController [@foo] | provenance | | +| CodeInjection.rb:118:10:118:13 | self : PostsController [@foo] | CodeInjection.rb:118:10:118:13 | @foo | provenance | | +models +| 1 | Summary: Regexp!; Method[escape,quote]; Argument[0]; ReturnValue; taint | nodes | CodeInjection.rb:5:5:5:8 | code | semmle.label | code | | CodeInjection.rb:5:12:5:17 | call to params | semmle.label | call to params | @@ -14,59 +57,18 @@ nodes | CodeInjection.rb:78:5:78:8 | code | semmle.label | code | | CodeInjection.rb:78:12:78:17 | call to params | semmle.label | call to params | | CodeInjection.rb:78:12:78:24 | ...[...] | semmle.label | ...[...] | -| CodeInjection.rb:80:16:80:19 | code | semmle.label | code | -| CodeInjection.rb:86:10:86:25 | ... + ... : [collection] [element] | semmle.label | ... + ... : [collection] [element] | -| CodeInjection.rb:86:10:86:37 | ... + ... | semmle.label | ... + ... | -| CodeInjection.rb:86:22:86:25 | code | semmle.label | code | -| CodeInjection.rb:88:10:88:32 | "prefix_#{...}_suffix" | semmle.label | "prefix_#{...}_suffix" | -| CodeInjection.rb:90:10:90:13 | code | semmle.label | code | -| CodeInjection.rb:101:3:102:5 | self in index : PostsController [@foo] | semmle.label | self in index : PostsController [@foo] | -| CodeInjection.rb:105:5:105:8 | [post] self [@foo] | semmle.label | [post] self [@foo] | -| CodeInjection.rb:105:12:105:17 | call to params | semmle.label | call to params | -| CodeInjection.rb:105:12:105:23 | ...[...] | semmle.label | ...[...] | -| CodeInjection.rb:108:3:109:5 | self in bar : PostsController [@foo] | semmle.label | self in bar : PostsController [@foo] | -| CodeInjection.rb:111:3:113:5 | self in baz : PostsController [@foo] | semmle.label | self in baz : PostsController [@foo] | -| CodeInjection.rb:112:10:112:13 | @foo | semmle.label | @foo | -| CodeInjection.rb:112:10:112:13 | self : PostsController [@foo] | semmle.label | self : PostsController [@foo] | -edges -| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:8:10:8:13 | code | provenance | | -| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:20:20:20:23 | code | provenance | | -| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:23:21:23:24 | code | provenance | | -| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:29:15:29:18 | code | provenance | | -| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:32:19:32:22 | code | provenance | | -| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:38:24:38:27 | code | provenance | | -| CodeInjection.rb:5:5:5:8 | code | CodeInjection.rb:41:40:41:43 | code | provenance | | -| CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:5:12:5:24 | ...[...] | provenance | | -| CodeInjection.rb:5:12:5:24 | ...[...] | CodeInjection.rb:5:5:5:8 | code | provenance | | -| CodeInjection.rb:38:24:38:27 | code | CodeInjection.rb:38:10:38:28 | call to escape | provenance | MaD:21 | -| CodeInjection.rb:78:5:78:8 | code | CodeInjection.rb:80:16:80:19 | code | provenance | | -| CodeInjection.rb:78:5:78:8 | code | CodeInjection.rb:86:10:86:37 | ... + ... | provenance | | -| CodeInjection.rb:78:5:78:8 | code | CodeInjection.rb:86:22:86:25 | code | provenance | | -| CodeInjection.rb:78:5:78:8 | code | CodeInjection.rb:88:10:88:32 | "prefix_#{...}_suffix" | provenance | AdditionalTaintStep | -| CodeInjection.rb:78:5:78:8 | code | CodeInjection.rb:90:10:90:13 | code | provenance | | -| CodeInjection.rb:78:12:78:17 | call to params | CodeInjection.rb:78:12:78:24 | ...[...] | provenance | | -| CodeInjection.rb:78:12:78:24 | ...[...] | CodeInjection.rb:78:5:78:8 | code | provenance | | -| CodeInjection.rb:86:10:86:25 | ... + ... : [collection] [element] | CodeInjection.rb:86:10:86:37 | ... + ... | provenance | | -| CodeInjection.rb:86:22:86:25 | code | CodeInjection.rb:86:10:86:25 | ... + ... : [collection] [element] | provenance | | -| CodeInjection.rb:101:3:102:5 | self in index : PostsController [@foo] | CodeInjection.rb:111:3:113:5 | self in baz : PostsController [@foo] | provenance | | -| CodeInjection.rb:105:5:105:8 | [post] self [@foo] | CodeInjection.rb:108:3:109:5 | self in bar : PostsController [@foo] | provenance | | -| CodeInjection.rb:105:12:105:17 | call to params | CodeInjection.rb:105:12:105:23 | ...[...] | provenance | | -| CodeInjection.rb:105:12:105:23 | ...[...] | CodeInjection.rb:105:5:105:8 | [post] self [@foo] | provenance | | -| CodeInjection.rb:108:3:109:5 | self in bar : PostsController [@foo] | CodeInjection.rb:101:3:102:5 | self in index : PostsController [@foo] | provenance | | -| CodeInjection.rb:111:3:113:5 | self in baz : PostsController [@foo] | CodeInjection.rb:112:10:112:13 | self : PostsController [@foo] | provenance | | -| CodeInjection.rb:112:10:112:13 | self : PostsController [@foo] | CodeInjection.rb:112:10:112:13 | @foo | provenance | | +| CodeInjection.rb:81:16:81:19 | code | semmle.label | code | +| CodeInjection.rb:90:10:90:25 | ... + ... : [collection] [element] | semmle.label | ... + ... : [collection] [element] | +| CodeInjection.rb:90:10:90:37 | ... + ... | semmle.label | ... + ... | +| CodeInjection.rb:90:22:90:25 | code | semmle.label | code | +| CodeInjection.rb:93:10:93:32 | "prefix_#{...}_suffix" | semmle.label | "prefix_#{...}_suffix" | +| CodeInjection.rb:96:10:96:13 | code | semmle.label | code | +| CodeInjection.rb:107:3:108:5 | self in index : PostsController [@foo] | semmle.label | self in index : PostsController [@foo] | +| CodeInjection.rb:111:5:111:8 | [post] self [@foo] | semmle.label | [post] self [@foo] | +| CodeInjection.rb:111:12:111:17 | call to params | semmle.label | call to params | +| CodeInjection.rb:111:12:111:23 | ...[...] | semmle.label | ...[...] | +| CodeInjection.rb:114:3:115:5 | self in bar : PostsController [@foo] | semmle.label | self in bar : PostsController [@foo] | +| CodeInjection.rb:117:3:119:5 | self in baz : PostsController [@foo] | semmle.label | self in baz : PostsController [@foo] | +| CodeInjection.rb:118:10:118:13 | @foo | semmle.label | @foo | +| CodeInjection.rb:118:10:118:13 | self : PostsController [@foo] | semmle.label | self : PostsController [@foo] | subpaths -#select -| CodeInjection.rb:8:10:8:13 | code | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:8:10:8:13 | code | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | -| CodeInjection.rb:11:10:11:15 | call to params | CodeInjection.rb:11:10:11:15 | call to params | CodeInjection.rb:11:10:11:15 | call to params | This code execution depends on a $@. | CodeInjection.rb:11:10:11:15 | call to params | user-provided value | -| CodeInjection.rb:20:20:20:23 | code | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:20:20:20:23 | code | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | -| CodeInjection.rb:23:21:23:24 | code | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:23:21:23:24 | code | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | -| CodeInjection.rb:29:15:29:18 | code | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:29:15:29:18 | code | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | -| CodeInjection.rb:32:19:32:22 | code | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:32:19:32:22 | code | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | -| CodeInjection.rb:38:10:38:28 | call to escape | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:38:10:38:28 | call to escape | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | -| CodeInjection.rb:41:40:41:43 | code | CodeInjection.rb:5:12:5:17 | call to params | CodeInjection.rb:41:40:41:43 | code | This code execution depends on a $@. | CodeInjection.rb:5:12:5:17 | call to params | user-provided value | -| CodeInjection.rb:80:16:80:19 | code | CodeInjection.rb:78:12:78:17 | call to params | CodeInjection.rb:80:16:80:19 | code | This code execution depends on a $@. | CodeInjection.rb:78:12:78:17 | call to params | user-provided value | -| CodeInjection.rb:86:10:86:37 | ... + ... | CodeInjection.rb:78:12:78:17 | call to params | CodeInjection.rb:86:10:86:37 | ... + ... | This code execution depends on a $@. | CodeInjection.rb:78:12:78:17 | call to params | user-provided value | -| CodeInjection.rb:88:10:88:32 | "prefix_#{...}_suffix" | CodeInjection.rb:78:12:78:17 | call to params | CodeInjection.rb:88:10:88:32 | "prefix_#{...}_suffix" | This code execution depends on a $@. | CodeInjection.rb:78:12:78:17 | call to params | user-provided value | -| CodeInjection.rb:90:10:90:13 | code | CodeInjection.rb:78:12:78:17 | call to params | CodeInjection.rb:90:10:90:13 | code | This code execution depends on a $@. | CodeInjection.rb:78:12:78:17 | call to params | user-provided value | -| CodeInjection.rb:112:10:112:13 | @foo | CodeInjection.rb:105:12:105:17 | call to params | CodeInjection.rb:112:10:112:13 | @foo | This code execution depends on a $@. | CodeInjection.rb:105:12:105:17 | call to params | user-provided value | diff --git a/ruby/ql/test/query-tests/security/cwe-094/CodeInjection/CodeInjection.qlref b/ruby/ql/test/query-tests/security/cwe-094/CodeInjection/CodeInjection.qlref index 6dcbcb4448ed..c62d0af0a1b8 100644 --- a/ruby/ql/test/query-tests/security/cwe-094/CodeInjection/CodeInjection.qlref +++ b/ruby/ql/test/query-tests/security/cwe-094/CodeInjection/CodeInjection.qlref @@ -1 +1,4 @@ -queries/security/cwe-094/CodeInjection.ql +query: queries/security/cwe-094/CodeInjection.ql +postprocess: + - utils/test/PrettyPrintModels.ql + - utils/test/InlineExpectationsTestQuery.ql diff --git a/ruby/ql/test/query-tests/security/cwe-094/CodeInjection/CodeInjection.rb b/ruby/ql/test/query-tests/security/cwe-094/CodeInjection/CodeInjection.rb index a8ed4a716136..f9c69d08e138 100644 --- a/ruby/ql/test/query-tests/security/cwe-094/CodeInjection/CodeInjection.rb +++ b/ruby/ql/test/query-tests/security/cwe-094/CodeInjection/CodeInjection.rb @@ -2,13 +2,13 @@ class UsersController < ActionController::Base def create - code = params[:code] + code = params[:code] # $ Source # BAD - eval(code) + eval(code) # $ Alert # BAD - eval(params) + eval(params) # $ Alert # GOOD - user input is in second argument, which is not evaluated as Ruby code send(:sanitize, params[:code]) @@ -17,28 +17,28 @@ def create Foo.new.bar(code) # BAD - Foo.class_eval(code) + Foo.class_eval(code) # $ Alert # BAD - Foo.module_eval(code) + Foo.module_eval(code) # $ Alert # GOOD Bar.class_eval(code) # BAD - const_get(code) + const_get(code) # $ Alert # BAD - Foo.const_get(code) + Foo.const_get(code) # $ Alert # GOOD Bar.const_get(code) # BAD - eval(Regexp.escape(code)) + eval(Regexp.escape(code)) # $ Alert # BAD - ActiveJob::Serializers.deserialize(code) + ActiveJob::Serializers.deserialize(code) # $ Alert end def update @@ -75,19 +75,25 @@ def self.const_get(x) class UsersController < ActionController::Base def create - code = params[:code] + code = params[:code] # $ Source - obj().send(code, "foo"); # BAD + # BAD + obj().send(code, "foo"); # $ Alert - obj().send("prefix_" + code + "_suffix", "foo"); # GOOD + # GOOD + obj().send("prefix_" + code + "_suffix", "foo"); - obj().send("prefix_#{code}_suffix", "foo"); # GOOD + # GOOD + obj().send("prefix_#{code}_suffix", "foo"); - eval("prefix_" + code + "_suffix"); # BAD + # BAD + eval("prefix_" + code + "_suffix"); # $ Alert - eval("prefix_#{code}_suffix"); # BAD + # BAD + eval("prefix_#{code}_suffix"); # $ Alert - eval(code); # BAD + # BAD + eval(code); # $ Alert end end @@ -102,13 +108,13 @@ def index end def foo - @foo = params[:foo] + @foo = params[:foo] # $ Source end def bar end def baz - eval(@foo) + eval(@foo) # $ Alert end end