From 7231db65d89828d86276fd44dfb1bc1677113de4 Mon Sep 17 00:00:00 2001 From: Chad Bentz <1760475+felickz@users.noreply.github.com> Date: Tue, 15 Sep 2026 19:58:03 -0400 Subject: [PATCH 1/5] Ruby: model Grape request sub-accessors as remote flow sources Adds request.body.read/.string, .params, .GET, .POST, .cookies, .env, .query_string, and .path_info as Http::Server::RequestInputAccess sources within Grape::API endpoints, plus matching Grape.ql test predicates and app.rb coverage. Also adds ConnectedToInjection.rb, exercising ActiveRecord connected_to role-switch blocks with captured variables as an SQL injection regression fixture. .expected files intentionally not yet updated pending a CodeQL CLI upgrade/test pass. --- ruby/ql/lib/codeql/ruby/frameworks/Grape.qll | 147 ++++++++++++++++++ .../library-tests/frameworks/grape/Grape.ql | 18 +++ .../library-tests/frameworks/grape/app.rb | 53 ++++++- .../security/cwe-089/ConnectedToInjection.rb | 37 +++++ 4 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 ruby/ql/test/query-tests/security/cwe-089/ConnectedToInjection.rb diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Grape.qll b/ruby/ql/lib/codeql/ruby/frameworks/Grape.qll index 9b7ae6185cdb..ff9439181265 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Grape.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Grape.qll @@ -206,6 +206,153 @@ module Grape { } } + /** + * Gets an API node for the `request` object accessible within a Grape API endpoint. + */ + private API::Node grapeRequestInstance() { result = grapeApiInstance().getReturn("request") } + + /** + * A call to `request.body.read` from within a Grape API endpoint, which reads + * the raw, unparsed request body and can contain user input. + */ + class GrapeRequestBodyReadSource extends Http::Server::RequestInputAccess::Range { + GrapeRequestBodyReadSource() { + this.asExpr().getExpr() = + grapeRequestInstance().getReturn("body").getAMethodCall("read").asExpr().getExpr() + } + + override string getSourceType() { result = "Grape::Request#body.read" } + + override Http::Server::RequestInputKind getKind() { + result = Http::Server::parameterInputKind() + } + } + + /** + * A call to `request.body.string` from within a Grape API endpoint, which reads + * the raw, unparsed request body and can contain user input. + */ + class GrapeRequestBodyStringSource extends Http::Server::RequestInputAccess::Range { + GrapeRequestBodyStringSource() { + this.asExpr().getExpr() = + grapeRequestInstance().getReturn("body").getAMethodCall("string").asExpr().getExpr() + } + + override string getSourceType() { result = "Grape::Request#body.string" } + + override Http::Server::RequestInputKind getKind() { + result = Http::Server::parameterInputKind() + } + } + + /** + * A call to `request.params` from within a Grape API endpoint. + */ + class GrapeRequestParamsSource extends Http::Server::RequestInputAccess::Range { + GrapeRequestParamsSource() { + this.asExpr().getExpr() = grapeRequestInstance().getAMethodCall("params").asExpr().getExpr() + } + + override string getSourceType() { result = "Grape::Request#params" } + + override Http::Server::RequestInputKind getKind() { + result = Http::Server::parameterInputKind() + } + } + + /** + * A call to `request.GET` from within a Grape API endpoint, which returns the + * parsed query-string parameters. + */ + class GrapeRequestGetSource extends Http::Server::RequestInputAccess::Range { + GrapeRequestGetSource() { + this.asExpr().getExpr() = grapeRequestInstance().getAMethodCall("GET").asExpr().getExpr() + } + + override string getSourceType() { result = "Grape::Request#GET" } + + override Http::Server::RequestInputKind getKind() { + result = Http::Server::parameterInputKind() + } + } + + /** + * A call to `request.POST` from within a Grape API endpoint, which returns the + * parsed form/body parameters. + */ + class GrapeRequestPostSource extends Http::Server::RequestInputAccess::Range { + GrapeRequestPostSource() { + this.asExpr().getExpr() = grapeRequestInstance().getAMethodCall("POST").asExpr().getExpr() + } + + override string getSourceType() { result = "Grape::Request#POST" } + + override Http::Server::RequestInputKind getKind() { + result = Http::Server::parameterInputKind() + } + } + + /** + * A call to `request.cookies` from within a Grape API endpoint. + */ + class GrapeRequestCookiesSource extends Http::Server::RequestInputAccess::Range { + GrapeRequestCookiesSource() { + this.asExpr().getExpr() = grapeRequestInstance().getAMethodCall("cookies").asExpr().getExpr() + } + + override string getSourceType() { result = "Grape::Request#cookies" } + + override Http::Server::RequestInputKind getKind() { result = Http::Server::cookieInputKind() } + } + + /** + * A call to `request.env` from within a Grape API endpoint, which returns the + * underlying Rack environment hash. + */ + class GrapeRequestEnvSource extends Http::Server::RequestInputAccess::Range { + GrapeRequestEnvSource() { + this.asExpr().getExpr() = grapeRequestInstance().getAMethodCall("env").asExpr().getExpr() + } + + override string getSourceType() { result = "Grape::Request#env" } + + override Http::Server::RequestInputKind getKind() { + result = Http::Server::parameterInputKind() + } + } + + /** + * A call to `request.query_string` from within a Grape API endpoint. + */ + class GrapeRequestQueryStringSource extends Http::Server::RequestInputAccess::Range { + GrapeRequestQueryStringSource() { + this.asExpr().getExpr() = + grapeRequestInstance().getAMethodCall("query_string").asExpr().getExpr() + } + + override string getSourceType() { result = "Grape::Request#query_string" } + + override Http::Server::RequestInputKind getKind() { + result = Http::Server::parameterInputKind() + } + } + + /** + * A call to `request.path_info` from within a Grape API endpoint. + */ + class GrapeRequestPathInfoSource extends Http::Server::RequestInputAccess::Range { + GrapeRequestPathInfoSource() { + this.asExpr().getExpr() = + grapeRequestInstance().getAMethodCall("path_info").asExpr().getExpr() + } + + override string getSourceType() { result = "Grape::Request#path_info" } + + override Http::Server::RequestInputKind getKind() { + result = Http::Server::parameterInputKind() + } + } + /** * A call to `route_param` from within a Grape API endpoint. */ diff --git a/ruby/ql/test/library-tests/frameworks/grape/Grape.ql b/ruby/ql/test/library-tests/frameworks/grape/Grape.ql index 63cd15a547e1..21d1fcfd0c16 100644 --- a/ruby/ql/test/library-tests/frameworks/grape/Grape.ql +++ b/ruby/ql/test/library-tests/frameworks/grape/Grape.ql @@ -22,3 +22,21 @@ query predicate grapeRequest(Grape::GrapeRequestSource request) { any() } query predicate grapeRouteParam(Grape::GrapeRouteParamSource routeParam) { any() } query predicate grapeCookies(Grape::GrapeCookiesSource cookies) { any() } + +query predicate grapeRequestBodyRead(Grape::GrapeRequestBodyReadSource src) { any() } + +query predicate grapeRequestBodyString(Grape::GrapeRequestBodyStringSource src) { any() } + +query predicate grapeRequestParams(Grape::GrapeRequestParamsSource src) { any() } + +query predicate grapeRequestGET(Grape::GrapeRequestGetSource src) { any() } + +query predicate grapeRequestPOST(Grape::GrapeRequestPostSource src) { any() } + +query predicate grapeRequestCookies(Grape::GrapeRequestCookiesSource src) { any() } + +query predicate grapeRequestEnv(Grape::GrapeRequestEnvSource src) { any() } + +query predicate grapeRequestQueryString(Grape::GrapeRequestQueryStringSource src) { any() } + +query predicate grapeRequestPathInfo(Grape::GrapeRequestPathInfoSource src) { any() } diff --git a/ruby/ql/test/library-tests/frameworks/grape/app.rb b/ruby/ql/test/library-tests/frameworks/grape/app.rb index 1b1fd15d5d8c..4148e9256768 100644 --- a/ruby/ql/test/library-tests/frameworks/grape/app.rb +++ b/ruby/ql/test/library-tests/frameworks/grape/app.rb @@ -174,7 +174,7 @@ def test_helper sink route_id # $ hasTaintFlow sink auth # $ hasTaintFlow sink session # $ hasTaintFlow - # Note: request.body.read may not be detected by this flow test config + sink body_data # $ hasTaintFlow end get '/helper_test/:user_id' do @@ -237,3 +237,54 @@ def test_helper end end end + +# Exercises `request` sub-accessors (body, params, GET, POST, cookies, env, +# query_string, path_info) as remote flow sources. +class RequestAccessorsAPI < Grape::API + format :json + + get '/body_read_test' do + body_data = request.body.read + sink body_data # $ hasTaintFlow + end + + get '/body_string_test' do + body_data = request.body.string + sink body_data # $ hasTaintFlow + end + + get '/request_params_test' do + params_hash = request.params + sink params_hash # $ hasTaintFlow + end + + get '/request_get_test' do + get_params = request.GET + sink get_params # $ hasTaintFlow + end + + get '/request_post_test' do + post_params = request.POST + sink post_params # $ hasTaintFlow + end + + get '/request_cookies_test' do + cookie_data = request.cookies + sink cookie_data # $ hasTaintFlow + end + + get '/request_env_test' do + env_data = request.env + sink env_data # $ hasTaintFlow + end + + get '/request_query_string_test' do + qs = request.query_string + sink qs # $ hasTaintFlow + end + + get '/request_path_info_test' do + path = request.path_info + sink path # $ hasTaintFlow + end +end diff --git a/ruby/ql/test/query-tests/security/cwe-089/ConnectedToInjection.rb b/ruby/ql/test/query-tests/security/cwe-089/ConnectedToInjection.rb new file mode 100644 index 000000000000..ef8f5f585b4c --- /dev/null +++ b/ruby/ql/test/query-tests/security/cwe-089/ConnectedToInjection.rb @@ -0,0 +1,37 @@ +class EventsController < ActionController::Base + # BAD: remote input captured by a block is interpolated into raw SQL + # executed inside an ActiveRecord `connected_to` role-switch block. + def date_range_report + lower_bound = params[:lower_bound] + upper_bound = params[:upper_bound] + + ApplicationRecord.connected_to(role: :reading) do + ApplicationRecord.connection.execute( + "SELECT * FROM events WHERE start_time BETWEEN '#{lower_bound}' AND '#{upper_bound}'" + ) + end + end + + # BAD: same pattern with a single captured bound + def since_report + lower_bound = params[:lower_bound] + + ApplicationRecord.connected_to(role: :reading) do + ApplicationRecord.connection.execute("SELECT * FROM events WHERE created_at > '#{lower_bound}'") + end + end + + # BAD: value read inside the block is assigned to an outer-scope variable, + # then interpolated into SQL executed after the block returns. + def deferred_report + lower_bound = params[:lower_bound] + captured = nil + + ApplicationRecord.connected_to(role: :reading) do + captured = lower_bound + end + + sql = "SELECT * FROM events WHERE created_at > '#{captured}'" + ApplicationRecord.connection.execute(sql) + end +end From 0c69604412d92a005d4208114a79a8be40afae91 Mon Sep 17 00:00:00 2001 From: Chad Bentz <1760475+felickz@users.noreply.github.com> Date: Tue, 15 Sep 2026 21:14:17 -0400 Subject: [PATCH 2/5] Ruby: add inline expectations and regenerate .expected for grape+cwe-089 tests Adds # $ Source / # $ Alert annotations to ConnectedToInjection.rb matching the sibling fixtures' convention. Regenerated Grape.expected/Flow.expected and SqlInjection.expected from verified actual output (codeql test run, CLI 2.27.0). All 3 tests pass: Flow.ql, Grape.ql, SqlInjection.qlref. --- .../frameworks/grape/Flow.expected | 60 +++++++++++++++++++ .../frameworks/grape/Grape.expected | 39 ++++++++++++ .../security/cwe-089/ConnectedToInjection.rb | 14 ++--- .../security/cwe-089/SqlInjection.expected | 44 ++++++++++++++ 4 files changed, 150 insertions(+), 7 deletions(-) diff --git a/ruby/ql/test/library-tests/frameworks/grape/Flow.expected b/ruby/ql/test/library-tests/frameworks/grape/Flow.expected index f04bd930ea97..23f8d78bb053 100644 --- a/ruby/ql/test/library-tests/frameworks/grape/Flow.expected +++ b/ruby/ql/test/library-tests/frameworks/grape/Flow.expected @@ -33,6 +33,8 @@ edges | app.rb:169:9:169:15 | session | app.rb:176:14:176:20 | session | provenance | | | app.rb:169:19:169:25 | call to cookies | app.rb:169:19:169:38 | ...[...] | provenance | | | app.rb:169:19:169:38 | ...[...] | app.rb:169:9:169:15 | session | provenance | | +| app.rb:170:9:170:17 | body_data | app.rb:177:14:177:22 | body_data | provenance | | +| app.rb:170:21:170:37 | call to read | app.rb:170:9:170:17 | body_data | provenance | | | app.rb:183:9:183:14 | result | app.rb:184:14:184:19 | result | provenance | | | app.rb:183:9:183:14 | result | app.rb:184:14:184:19 | result | provenance | | | app.rb:183:18:183:43 | call to vulnerable_helper | app.rb:183:9:183:14 | result | provenance | | @@ -76,6 +78,24 @@ edges | app.rb:235:13:235:23 | case_result | app.rb:236:18:236:28 | case_result | provenance | | | app.rb:235:27:235:37 | call to test_helper | app.rb:235:13:235:23 | case_result | provenance | | | app.rb:235:27:235:37 | call to test_helper | app.rb:235:13:235:23 | case_result | provenance | | +| app.rb:247:5:247:13 | body_data | app.rb:248:10:248:18 | body_data | provenance | | +| app.rb:247:17:247:33 | call to read | app.rb:247:5:247:13 | body_data | provenance | | +| app.rb:252:5:252:13 | body_data | app.rb:253:10:253:18 | body_data | provenance | | +| app.rb:252:17:252:35 | call to string | app.rb:252:5:252:13 | body_data | provenance | | +| app.rb:257:5:257:15 | params_hash | app.rb:258:10:258:20 | params_hash | provenance | | +| app.rb:257:19:257:32 | call to params | app.rb:257:5:257:15 | params_hash | provenance | | +| app.rb:262:5:262:14 | get_params | app.rb:263:10:263:19 | get_params | provenance | | +| app.rb:262:18:262:28 | call to GET | app.rb:262:5:262:14 | get_params | provenance | | +| app.rb:267:5:267:15 | post_params | app.rb:268:10:268:20 | post_params | provenance | | +| app.rb:267:19:267:30 | call to POST | app.rb:267:5:267:15 | post_params | provenance | | +| app.rb:272:5:272:15 | cookie_data | app.rb:273:10:273:20 | cookie_data | provenance | | +| app.rb:272:19:272:33 | call to cookies | app.rb:272:5:272:15 | cookie_data | provenance | | +| app.rb:277:5:277:12 | env_data | app.rb:278:10:278:17 | env_data | provenance | | +| app.rb:277:16:277:26 | call to env | app.rb:277:5:277:12 | env_data | provenance | | +| app.rb:282:5:282:6 | qs | app.rb:283:10:283:11 | qs | provenance | | +| app.rb:282:10:282:29 | call to query_string | app.rb:282:5:282:6 | qs | provenance | | +| app.rb:287:5:287:8 | path | app.rb:288:10:288:13 | path | provenance | | +| app.rb:287:12:287:28 | call to path_info | app.rb:287:5:287:8 | path | provenance | | nodes | app.rb:103:13:103:18 | call to params | semmle.label | call to params | | app.rb:103:13:103:70 | call to select | semmle.label | call to select | @@ -107,10 +127,13 @@ nodes | app.rb:169:9:169:15 | session | semmle.label | session | | app.rb:169:19:169:25 | call to cookies | semmle.label | call to cookies | | app.rb:169:19:169:38 | ...[...] | semmle.label | ...[...] | +| app.rb:170:9:170:17 | body_data | semmle.label | body_data | +| app.rb:170:21:170:37 | call to read | semmle.label | call to read | | app.rb:173:14:173:20 | user_id | semmle.label | user_id | | app.rb:174:14:174:21 | route_id | semmle.label | route_id | | app.rb:175:14:175:17 | auth | semmle.label | auth | | app.rb:176:14:176:20 | session | semmle.label | session | +| app.rb:177:14:177:22 | body_data | semmle.label | body_data | | app.rb:183:9:183:14 | result | semmle.label | result | | app.rb:183:9:183:14 | result | semmle.label | result | | app.rb:183:18:183:43 | call to vulnerable_helper | semmle.label | call to vulnerable_helper | @@ -173,6 +196,33 @@ nodes | app.rb:235:27:235:37 | call to test_helper | semmle.label | call to test_helper | | app.rb:236:18:236:28 | case_result | semmle.label | case_result | | app.rb:236:18:236:28 | case_result | semmle.label | case_result | +| app.rb:247:5:247:13 | body_data | semmle.label | body_data | +| app.rb:247:17:247:33 | call to read | semmle.label | call to read | +| app.rb:248:10:248:18 | body_data | semmle.label | body_data | +| app.rb:252:5:252:13 | body_data | semmle.label | body_data | +| app.rb:252:17:252:35 | call to string | semmle.label | call to string | +| app.rb:253:10:253:18 | body_data | semmle.label | body_data | +| app.rb:257:5:257:15 | params_hash | semmle.label | params_hash | +| app.rb:257:19:257:32 | call to params | semmle.label | call to params | +| app.rb:258:10:258:20 | params_hash | semmle.label | params_hash | +| app.rb:262:5:262:14 | get_params | semmle.label | get_params | +| app.rb:262:18:262:28 | call to GET | semmle.label | call to GET | +| app.rb:263:10:263:19 | get_params | semmle.label | get_params | +| app.rb:267:5:267:15 | post_params | semmle.label | post_params | +| app.rb:267:19:267:30 | call to POST | semmle.label | call to POST | +| app.rb:268:10:268:20 | post_params | semmle.label | post_params | +| app.rb:272:5:272:15 | cookie_data | semmle.label | cookie_data | +| app.rb:272:19:272:33 | call to cookies | semmle.label | call to cookies | +| app.rb:273:10:273:20 | cookie_data | semmle.label | cookie_data | +| app.rb:277:5:277:12 | env_data | semmle.label | env_data | +| app.rb:277:16:277:26 | call to env | semmle.label | call to env | +| app.rb:278:10:278:17 | env_data | semmle.label | env_data | +| app.rb:282:5:282:6 | qs | semmle.label | qs | +| app.rb:282:10:282:29 | call to query_string | semmle.label | call to query_string | +| app.rb:283:10:283:11 | qs | semmle.label | qs | +| app.rb:287:5:287:8 | path | semmle.label | path | +| app.rb:287:12:287:28 | call to path_info | semmle.label | call to path_info | +| app.rb:288:10:288:13 | path | semmle.label | path | subpaths testFailures #select @@ -180,6 +230,7 @@ testFailures | app.rb:174:14:174:21 | route_id | app.rb:167:20:167:40 | call to route_param | app.rb:174:14:174:21 | route_id | $@ | app.rb:167:20:167:40 | call to route_param | call to route_param | | app.rb:175:14:175:17 | auth | app.rb:168:16:168:22 | call to headers | app.rb:175:14:175:17 | auth | $@ | app.rb:168:16:168:22 | call to headers | call to headers | | app.rb:176:14:176:20 | session | app.rb:169:19:169:25 | call to cookies | app.rb:176:14:176:20 | session | $@ | app.rb:169:19:169:25 | call to cookies | call to cookies | +| app.rb:177:14:177:22 | body_data | app.rb:170:21:170:37 | call to read | app.rb:177:14:177:22 | body_data | $@ | app.rb:170:21:170:37 | call to read | call to read | | app.rb:184:14:184:19 | result | app.rb:107:13:107:32 | call to source | app.rb:184:14:184:19 | result | $@ | app.rb:107:13:107:32 | call to source | call to source | | app.rb:184:14:184:19 | result | app.rb:107:13:107:32 | call to source | app.rb:184:14:184:19 | result | $@ | app.rb:107:13:107:32 | call to source | call to source | | app.rb:191:14:191:22 | user_data | app.rb:103:13:103:18 | call to params | app.rb:191:14:191:22 | user_data | $@ | app.rb:103:13:103:18 | call to params | call to params | @@ -199,3 +250,12 @@ testFailures | app.rb:231:14:231:26 | rescue_result | app.rb:140:17:140:37 | call to source | app.rb:231:14:231:26 | rescue_result | $@ | app.rb:140:17:140:37 | call to source | call to source | | app.rb:236:18:236:28 | case_result | app.rb:150:17:150:35 | call to source | app.rb:236:18:236:28 | case_result | $@ | app.rb:150:17:150:35 | call to source | call to source | | app.rb:236:18:236:28 | case_result | app.rb:150:17:150:35 | call to source | app.rb:236:18:236:28 | case_result | $@ | app.rb:150:17:150:35 | call to source | call to source | +| app.rb:248:10:248:18 | body_data | app.rb:247:17:247:33 | call to read | app.rb:248:10:248:18 | body_data | $@ | app.rb:247:17:247:33 | call to read | call to read | +| app.rb:253:10:253:18 | body_data | app.rb:252:17:252:35 | call to string | app.rb:253:10:253:18 | body_data | $@ | app.rb:252:17:252:35 | call to string | call to string | +| app.rb:258:10:258:20 | params_hash | app.rb:257:19:257:32 | call to params | app.rb:258:10:258:20 | params_hash | $@ | app.rb:257:19:257:32 | call to params | call to params | +| app.rb:263:10:263:19 | get_params | app.rb:262:18:262:28 | call to GET | app.rb:263:10:263:19 | get_params | $@ | app.rb:262:18:262:28 | call to GET | call to GET | +| app.rb:268:10:268:20 | post_params | app.rb:267:19:267:30 | call to POST | app.rb:268:10:268:20 | post_params | $@ | app.rb:267:19:267:30 | call to POST | call to POST | +| app.rb:273:10:273:20 | cookie_data | app.rb:272:19:272:33 | call to cookies | app.rb:273:10:273:20 | cookie_data | $@ | app.rb:272:19:272:33 | call to cookies | call to cookies | +| app.rb:278:10:278:17 | env_data | app.rb:277:16:277:26 | call to env | app.rb:278:10:278:17 | env_data | $@ | app.rb:277:16:277:26 | call to env | call to env | +| app.rb:283:10:283:11 | qs | app.rb:282:10:282:29 | call to query_string | app.rb:283:10:283:11 | qs | $@ | app.rb:282:10:282:29 | call to query_string | call to query_string | +| app.rb:288:10:288:13 | path | app.rb:287:12:287:28 | call to path_info | app.rb:288:10:288:13 | path | $@ | app.rb:287:12:287:28 | call to path_info | call to path_info | diff --git a/ruby/ql/test/library-tests/frameworks/grape/Grape.expected b/ruby/ql/test/library-tests/frameworks/grape/Grape.expected index 7088eeb9018a..574322e208ce 100644 --- a/ruby/ql/test/library-tests/frameworks/grape/Grape.expected +++ b/ruby/ql/test/library-tests/frameworks/grape/Grape.expected @@ -2,6 +2,7 @@ grapeApiClasses | app.rb:1:1:90:3 | MyAPI | | app.rb:92:1:96:3 | AdminAPI | | app.rb:98:1:239:3 | UserAPI | +| app.rb:243:1:290:3 | RequestAccessorsAPI | grapeEndpoints | app.rb:1:1:90:3 | MyAPI | app.rb:7:3:11:5 | call to get | GET | /hello/:name | | app.rb:1:1:90:3 | MyAPI | app.rb:17:3:20:5 | call to post | POST | /messages | @@ -19,6 +20,15 @@ grapeEndpoints | app.rb:98:1:239:3 | UserAPI | app.rb:187:5:193:7 | call to post | POST | /users | | app.rb:98:1:239:3 | UserAPI | app.rb:204:5:207:7 | call to post | POST | /users | | app.rb:98:1:239:3 | UserAPI | app.rb:210:5:238:7 | call to get | GET | /nested_test/:token | +| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:246:3:249:5 | call to get | GET | /body_read_test | +| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:251:3:254:5 | call to get | GET | /body_string_test | +| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:256:3:259:5 | call to get | GET | /request_params_test | +| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:261:3:264:5 | call to get | GET | /request_get_test | +| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:266:3:269:5 | call to get | GET | /request_post_test | +| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:271:3:274:5 | call to get | GET | /request_cookies_test | +| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:276:3:279:5 | call to get | GET | /request_env_test | +| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:281:3:284:5 | call to get | GET | /request_query_string_test | +| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:286:3:289:5 | call to get | GET | /request_path_info_test | grapeParams | app.rb:8:12:8:17 | call to params | | app.rb:14:3:16:5 | call to params | @@ -44,6 +54,15 @@ grapeHeaders grapeRequest | app.rb:25:12:25:18 | call to request | | app.rb:170:21:170:27 | call to request | +| app.rb:247:17:247:23 | call to request | +| app.rb:252:17:252:23 | call to request | +| app.rb:257:19:257:25 | call to request | +| app.rb:262:18:262:24 | call to request | +| app.rb:267:19:267:25 | call to request | +| app.rb:272:19:272:25 | call to request | +| app.rb:277:16:277:22 | call to request | +| app.rb:282:10:282:16 | call to request | +| app.rb:287:12:287:18 | call to request | grapeRouteParam | app.rb:51:15:51:35 | call to route_param | | app.rb:52:15:52:36 | call to route_param | @@ -56,3 +75,23 @@ grapeCookies | app.rb:80:16:80:22 | call to cookies | | app.rb:160:5:162:7 | call to cookies | | app.rb:169:19:169:25 | call to cookies | +grapeRequestBodyRead +| app.rb:25:12:25:28 | call to read | +| app.rb:170:21:170:37 | call to read | +| app.rb:247:17:247:33 | call to read | +grapeRequestBodyString +| app.rb:252:17:252:35 | call to string | +grapeRequestParams +| app.rb:257:19:257:32 | call to params | +grapeRequestGET +| app.rb:262:18:262:28 | call to GET | +grapeRequestPOST +| app.rb:267:19:267:30 | call to POST | +grapeRequestCookies +| app.rb:272:19:272:33 | call to cookies | +grapeRequestEnv +| app.rb:277:16:277:26 | call to env | +grapeRequestQueryString +| app.rb:282:10:282:29 | call to query_string | +grapeRequestPathInfo +| app.rb:287:12:287:28 | call to path_info | diff --git a/ruby/ql/test/query-tests/security/cwe-089/ConnectedToInjection.rb b/ruby/ql/test/query-tests/security/cwe-089/ConnectedToInjection.rb index ef8f5f585b4c..4d57fc9bbe93 100644 --- a/ruby/ql/test/query-tests/security/cwe-089/ConnectedToInjection.rb +++ b/ruby/ql/test/query-tests/security/cwe-089/ConnectedToInjection.rb @@ -2,29 +2,29 @@ class EventsController < ActionController::Base # BAD: remote input captured by a block is interpolated into raw SQL # executed inside an ActiveRecord `connected_to` role-switch block. def date_range_report - lower_bound = params[:lower_bound] - upper_bound = params[:upper_bound] + lower_bound = params[:lower_bound] # $ Source + upper_bound = params[:upper_bound] # $ Source ApplicationRecord.connected_to(role: :reading) do ApplicationRecord.connection.execute( - "SELECT * FROM events WHERE start_time BETWEEN '#{lower_bound}' AND '#{upper_bound}'" + "SELECT * FROM events WHERE start_time BETWEEN '#{lower_bound}' AND '#{upper_bound}'" # $ Alert ) end end # BAD: same pattern with a single captured bound def since_report - lower_bound = params[:lower_bound] + lower_bound = params[:lower_bound] # $ Source ApplicationRecord.connected_to(role: :reading) do - ApplicationRecord.connection.execute("SELECT * FROM events WHERE created_at > '#{lower_bound}'") + ApplicationRecord.connection.execute("SELECT * FROM events WHERE created_at > '#{lower_bound}'") # $ Alert end end # BAD: value read inside the block is assigned to an outer-scope variable, # then interpolated into SQL executed after the block returns. def deferred_report - lower_bound = params[:lower_bound] + lower_bound = params[:lower_bound] # $ Source captured = nil ApplicationRecord.connected_to(role: :reading) do @@ -32,6 +32,6 @@ def deferred_report end sql = "SELECT * FROM events WHERE created_at > '#{captured}'" - ApplicationRecord.connection.execute(sql) + ApplicationRecord.connection.execute(sql) # $ Alert end end diff --git a/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.expected b/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.expected index c8926f635c47..590f3bfd104d 100644 --- a/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.expected +++ b/ruby/ql/test/query-tests/security/cwe-089/SqlInjection.expected @@ -41,6 +41,10 @@ | ActiveRecordInjection.rb:217:38:217:53 | "role = #{...}" | ActiveRecordInjection.rb:223:29:223:34 | call to params | ActiveRecordInjection.rb:217:38:217:53 | "role = #{...}" | This SQL query depends on a $@. | ActiveRecordInjection.rb:223:29:223:34 | call to params | user-provided value | | ArelInjection.rb:6:20:6:61 | "SELECT * FROM users WHERE nam..." | ArelInjection.rb:4:12:4:17 | call to params | ArelInjection.rb:6:20:6:61 | "SELECT * FROM users WHERE nam..." | This SQL query depends on a $@. | ArelInjection.rb:4:12:4:17 | call to params | user-provided value | | ArelInjection.rb:7:39:7:80 | "SELECT * FROM users WHERE nam..." | ArelInjection.rb:4:12:4:17 | call to params | ArelInjection.rb:7:39:7:80 | "SELECT * FROM users WHERE nam..." | This SQL query depends on a $@. | ArelInjection.rb:4:12:4:17 | call to params | user-provided value | +| ConnectedToInjection.rb:10:9:10:93 | "SELECT * FROM events WHERE st..." | ConnectedToInjection.rb:5:19:5:24 | call to params | ConnectedToInjection.rb:10:9:10:93 | "SELECT * FROM events WHERE st..." | This SQL query depends on a $@. | ConnectedToInjection.rb:5:19:5:24 | call to params | user-provided value | +| ConnectedToInjection.rb:10:9:10:93 | "SELECT * FROM events WHERE st..." | ConnectedToInjection.rb:6:19:6:24 | call to params | ConnectedToInjection.rb:10:9:10:93 | "SELECT * FROM events WHERE st..." | This SQL query depends on a $@. | ConnectedToInjection.rb:6:19:6:24 | call to params | user-provided value | +| ConnectedToInjection.rb:20:44:20:101 | "SELECT * FROM events WHERE cr..." | ConnectedToInjection.rb:17:19:17:24 | call to params | ConnectedToInjection.rb:20:44:20:101 | "SELECT * FROM events WHERE cr..." | This SQL query depends on a $@. | ConnectedToInjection.rb:17:19:17:24 | call to params | user-provided value | +| ConnectedToInjection.rb:35:42:35:44 | sql | ConnectedToInjection.rb:27:19:27:24 | call to params | ConnectedToInjection.rb:35:42:35:44 | sql | This SQL query depends on a $@. | ConnectedToInjection.rb:27:19:27:24 | call to params | user-provided value | | PgInjection.rb:14:15:14:18 | qry1 | PgInjection.rb:6:12:6:17 | call to params | PgInjection.rb:14:15:14:18 | qry1 | This SQL query depends on a $@. | PgInjection.rb:6:12:6:17 | call to params | user-provided value | | PgInjection.rb:15:21:15:24 | qry1 | PgInjection.rb:6:12:6:17 | call to params | PgInjection.rb:15:21:15:24 | qry1 | This SQL query depends on a $@. | PgInjection.rb:6:12:6:17 | call to params | user-provided value | | PgInjection.rb:20:22:20:25 | qry2 | PgInjection.rb:6:12:6:17 | call to params | PgInjection.rb:20:22:20:25 | qry2 | This SQL query depends on a $@. | PgInjection.rb:6:12:6:17 | call to params | user-provided value | @@ -130,6 +134,24 @@ edges | ArelInjection.rb:4:5:4:8 | name | ArelInjection.rb:7:39:7:80 | "SELECT * FROM users WHERE nam..." | provenance | AdditionalTaintStep | | ArelInjection.rb:4:12:4:17 | call to params | ArelInjection.rb:4:12:4:29 | ...[...] | provenance | | | ArelInjection.rb:4:12:4:29 | ...[...] | ArelInjection.rb:4:5:4:8 | name | provenance | | +| ConnectedToInjection.rb:5:19:5:24 | call to params | ConnectedToInjection.rb:5:19:5:38 | ...[...] | provenance | | +| ConnectedToInjection.rb:5:19:5:38 | ...[...] | ConnectedToInjection.rb:8:52:12:7 | do ... end : [lambda] [captured lower_bound] | provenance | | +| ConnectedToInjection.rb:6:19:6:24 | call to params | ConnectedToInjection.rb:6:19:6:38 | ...[...] | provenance | | +| ConnectedToInjection.rb:6:19:6:38 | ...[...] | ConnectedToInjection.rb:8:52:12:7 | do ... end : [lambda] [captured upper_bound] | provenance | | +| ConnectedToInjection.rb:8:52:12:7 | do ... end : [lambda] [captured lower_bound] | ConnectedToInjection.rb:10:59:10:69 | lower_bound | provenance | heuristic-callback | +| ConnectedToInjection.rb:8:52:12:7 | do ... end : [lambda] [captured upper_bound] | ConnectedToInjection.rb:10:80:10:90 | upper_bound | provenance | heuristic-callback | +| ConnectedToInjection.rb:10:59:10:69 | lower_bound | ConnectedToInjection.rb:10:9:10:93 | "SELECT * FROM events WHERE st..." | provenance | AdditionalTaintStep | +| ConnectedToInjection.rb:10:80:10:90 | upper_bound | ConnectedToInjection.rb:10:9:10:93 | "SELECT * FROM events WHERE st..." | provenance | AdditionalTaintStep | +| ConnectedToInjection.rb:17:19:17:24 | call to params | ConnectedToInjection.rb:17:19:17:38 | ...[...] | provenance | | +| ConnectedToInjection.rb:17:19:17:38 | ...[...] | ConnectedToInjection.rb:19:52:21:7 | do ... end : [lambda] [captured lower_bound] | provenance | | +| ConnectedToInjection.rb:19:52:21:7 | do ... end : [lambda] [captured lower_bound] | ConnectedToInjection.rb:20:88:20:98 | lower_bound | provenance | heuristic-callback | +| ConnectedToInjection.rb:20:88:20:98 | lower_bound | ConnectedToInjection.rb:20:44:20:101 | "SELECT * FROM events WHERE cr..." | provenance | AdditionalTaintStep | +| ConnectedToInjection.rb:27:19:27:24 | call to params | ConnectedToInjection.rb:27:19:27:38 | ...[...] | provenance | | +| ConnectedToInjection.rb:27:19:27:38 | ...[...] | ConnectedToInjection.rb:30:52:32:7 | do ... end : [lambda] [captured lower_bound] | provenance | | +| ConnectedToInjection.rb:30:52:32:7 | [post] do ... end : [lambda] [captured captured] | ConnectedToInjection.rb:34:5:34:7 | sql : String | provenance | AdditionalTaintStep | +| ConnectedToInjection.rb:30:52:32:7 | do ... end : [lambda] [captured lower_bound] | ConnectedToInjection.rb:30:52:32:7 | [post] do ... end : [lambda] [captured captured] | provenance | heuristic-callback | +| ConnectedToInjection.rb:30:52:32:7 | do ... end : [lambda] [captured lower_bound] | ConnectedToInjection.rb:31:18:31:28 | lower_bound | provenance | heuristic-callback | +| ConnectedToInjection.rb:34:5:34:7 | sql : String | ConnectedToInjection.rb:35:42:35:44 | sql | provenance | | | PgInjection.rb:6:5:6:8 | name | PgInjection.rb:13:5:13:8 | qry1 : String | provenance | AdditionalTaintStep | | PgInjection.rb:6:5:6:8 | name | PgInjection.rb:19:5:19:8 | qry2 : String | provenance | AdditionalTaintStep | | PgInjection.rb:6:5:6:8 | name | PgInjection.rb:31:5:31:8 | qry3 : String | provenance | AdditionalTaintStep | @@ -258,6 +280,27 @@ nodes | ArelInjection.rb:4:12:4:29 | ...[...] | semmle.label | ...[...] | | ArelInjection.rb:6:20:6:61 | "SELECT * FROM users WHERE nam..." | semmle.label | "SELECT * FROM users WHERE nam..." | | ArelInjection.rb:7:39:7:80 | "SELECT * FROM users WHERE nam..." | semmle.label | "SELECT * FROM users WHERE nam..." | +| ConnectedToInjection.rb:5:19:5:24 | call to params | semmle.label | call to params | +| ConnectedToInjection.rb:5:19:5:38 | ...[...] | semmle.label | ...[...] | +| ConnectedToInjection.rb:6:19:6:24 | call to params | semmle.label | call to params | +| ConnectedToInjection.rb:6:19:6:38 | ...[...] | semmle.label | ...[...] | +| ConnectedToInjection.rb:8:52:12:7 | do ... end : [lambda] [captured lower_bound] | semmle.label | do ... end : [lambda] [captured lower_bound] | +| ConnectedToInjection.rb:8:52:12:7 | do ... end : [lambda] [captured upper_bound] | semmle.label | do ... end : [lambda] [captured upper_bound] | +| ConnectedToInjection.rb:10:9:10:93 | "SELECT * FROM events WHERE st..." | semmle.label | "SELECT * FROM events WHERE st..." | +| ConnectedToInjection.rb:10:59:10:69 | lower_bound | semmle.label | lower_bound | +| ConnectedToInjection.rb:10:80:10:90 | upper_bound | semmle.label | upper_bound | +| ConnectedToInjection.rb:17:19:17:24 | call to params | semmle.label | call to params | +| ConnectedToInjection.rb:17:19:17:38 | ...[...] | semmle.label | ...[...] | +| ConnectedToInjection.rb:19:52:21:7 | do ... end : [lambda] [captured lower_bound] | semmle.label | do ... end : [lambda] [captured lower_bound] | +| ConnectedToInjection.rb:20:44:20:101 | "SELECT * FROM events WHERE cr..." | semmle.label | "SELECT * FROM events WHERE cr..." | +| ConnectedToInjection.rb:20:88:20:98 | lower_bound | semmle.label | lower_bound | +| ConnectedToInjection.rb:27:19:27:24 | call to params | semmle.label | call to params | +| ConnectedToInjection.rb:27:19:27:38 | ...[...] | semmle.label | ...[...] | +| ConnectedToInjection.rb:30:52:32:7 | [post] do ... end : [lambda] [captured captured] | semmle.label | [post] do ... end : [lambda] [captured captured] | +| ConnectedToInjection.rb:30:52:32:7 | do ... end : [lambda] [captured lower_bound] | semmle.label | do ... end : [lambda] [captured lower_bound] | +| ConnectedToInjection.rb:31:18:31:28 | lower_bound | semmle.label | lower_bound | +| ConnectedToInjection.rb:34:5:34:7 | sql : String | semmle.label | sql : String | +| ConnectedToInjection.rb:35:42:35:44 | sql | semmle.label | sql | | PgInjection.rb:6:5:6:8 | name | semmle.label | name | | PgInjection.rb:6:12:6:17 | call to params | semmle.label | call to params | | PgInjection.rb:6:12:6:24 | ...[...] | semmle.label | ...[...] | @@ -272,3 +315,4 @@ nodes | PgInjection.rb:43:5:43:8 | qry3 : String | semmle.label | qry3 : String | | PgInjection.rb:44:29:44:32 | qry3 | semmle.label | qry3 | subpaths +| ConnectedToInjection.rb:30:52:32:7 | do ... end : [lambda] [captured lower_bound] | ConnectedToInjection.rb:31:18:31:28 | lower_bound | ConnectedToInjection.rb:31:18:31:28 | lower_bound | ConnectedToInjection.rb:30:52:32:7 | [post] do ... end : [lambda] [captured captured] | From 0552e750311ba433517d058ba9566c0227ccef0d Mon Sep 17 00:00:00 2001 From: Chad Bentz <1760475+felickz@users.noreply.github.com> Date: Tue, 15 Sep 2026 21:37:24 -0400 Subject: [PATCH 3/5] Ruby: add change note for Grape request sub-accessor sources --- ruby/ql/lib/change-notes/2026-09-15-grape-request-sources.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 ruby/ql/lib/change-notes/2026-09-15-grape-request-sources.md diff --git a/ruby/ql/lib/change-notes/2026-09-15-grape-request-sources.md b/ruby/ql/lib/change-notes/2026-09-15-grape-request-sources.md new file mode 100644 index 000000000000..6c10c221b3ab --- /dev/null +++ b/ruby/ql/lib/change-notes/2026-09-15-grape-request-sources.md @@ -0,0 +1,4 @@ +--- +category: feature +--- +* Added modeling for `Grape::API`'s `request` sub-accessors (`body.read`, `body.string`, `params`, `GET`, `POST`, `cookies`, `env`, `query_string`, and `path_info`) as remote flow sources. From 9aabf067ac1ed5c10e7b9905f014149f1e4c8a1a Mon Sep 17 00:00:00 2001 From: Chad Bentz <1760475+felickz@users.noreply.github.com> Date: Tue, 15 Sep 2026 21:46:01 -0400 Subject: [PATCH 4/5] Ruby: fix acronym casing in Grape.ql predicate names grapeRequestGET/grapeRequestPOST -> grapeRequestGet/grapeRequestPost, per CodeQL code-scanning naming-convention review comments on the PR. --- ruby/ql/test/library-tests/frameworks/grape/Grape.expected | 4 ++-- ruby/ql/test/library-tests/frameworks/grape/Grape.ql | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ruby/ql/test/library-tests/frameworks/grape/Grape.expected b/ruby/ql/test/library-tests/frameworks/grape/Grape.expected index 574322e208ce..8551596024fb 100644 --- a/ruby/ql/test/library-tests/frameworks/grape/Grape.expected +++ b/ruby/ql/test/library-tests/frameworks/grape/Grape.expected @@ -83,9 +83,9 @@ grapeRequestBodyString | app.rb:252:17:252:35 | call to string | grapeRequestParams | app.rb:257:19:257:32 | call to params | -grapeRequestGET +grapeRequestGet | app.rb:262:18:262:28 | call to GET | -grapeRequestPOST +grapeRequestPost | app.rb:267:19:267:30 | call to POST | grapeRequestCookies | app.rb:272:19:272:33 | call to cookies | diff --git a/ruby/ql/test/library-tests/frameworks/grape/Grape.ql b/ruby/ql/test/library-tests/frameworks/grape/Grape.ql index 21d1fcfd0c16..09997b2c5b56 100644 --- a/ruby/ql/test/library-tests/frameworks/grape/Grape.ql +++ b/ruby/ql/test/library-tests/frameworks/grape/Grape.ql @@ -29,9 +29,9 @@ query predicate grapeRequestBodyString(Grape::GrapeRequestBodyStringSource src) query predicate grapeRequestParams(Grape::GrapeRequestParamsSource src) { any() } -query predicate grapeRequestGET(Grape::GrapeRequestGetSource src) { any() } +query predicate grapeRequestGet(Grape::GrapeRequestGetSource src) { any() } -query predicate grapeRequestPOST(Grape::GrapeRequestPostSource src) { any() } +query predicate grapeRequestPost(Grape::GrapeRequestPostSource src) { any() } query predicate grapeRequestCookies(Grape::GrapeRequestCookiesSource src) { any() } From cf909e553a333e62f65ef222529f8e15f68d8d7f Mon Sep 17 00:00:00 2001 From: Chad Bentz <1760475+felickz@users.noreply.github.com> Date: Thu, 17 Sep 2026 14:15:11 -0400 Subject: [PATCH 5/5] Ruby: fix Grape request input kinds --- ruby/ql/lib/codeql/ruby/frameworks/Grape.qll | 8 ++--- .../frameworks/grape/Grape.expected | 22 ++++++------ .../library-tests/frameworks/grape/Grape.ql | 36 ++++++++++++++----- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/ruby/ql/lib/codeql/ruby/frameworks/Grape.qll b/ruby/ql/lib/codeql/ruby/frameworks/Grape.qll index ff9439181265..b3d23e13dc84 100644 --- a/ruby/ql/lib/codeql/ruby/frameworks/Grape.qll +++ b/ruby/ql/lib/codeql/ruby/frameworks/Grape.qll @@ -224,7 +224,7 @@ module Grape { override string getSourceType() { result = "Grape::Request#body.read" } override Http::Server::RequestInputKind getKind() { - result = Http::Server::parameterInputKind() + result = Http::Server::bodyInputKind() } } @@ -241,7 +241,7 @@ module Grape { override string getSourceType() { result = "Grape::Request#body.string" } override Http::Server::RequestInputKind getKind() { - result = Http::Server::parameterInputKind() + result = Http::Server::bodyInputKind() } } @@ -288,7 +288,7 @@ module Grape { override string getSourceType() { result = "Grape::Request#POST" } override Http::Server::RequestInputKind getKind() { - result = Http::Server::parameterInputKind() + result = Http::Server::bodyInputKind() } } @@ -349,7 +349,7 @@ module Grape { override string getSourceType() { result = "Grape::Request#path_info" } override Http::Server::RequestInputKind getKind() { - result = Http::Server::parameterInputKind() + result = Http::Server::urlInputKind() } } diff --git a/ruby/ql/test/library-tests/frameworks/grape/Grape.expected b/ruby/ql/test/library-tests/frameworks/grape/Grape.expected index 8551596024fb..778eeb050849 100644 --- a/ruby/ql/test/library-tests/frameworks/grape/Grape.expected +++ b/ruby/ql/test/library-tests/frameworks/grape/Grape.expected @@ -76,22 +76,22 @@ grapeCookies | app.rb:160:5:162:7 | call to cookies | | app.rb:169:19:169:25 | call to cookies | grapeRequestBodyRead -| app.rb:25:12:25:28 | call to read | -| app.rb:170:21:170:37 | call to read | -| app.rb:247:17:247:33 | call to read | +| app.rb:25:12:25:28 | call to read | body | +| app.rb:170:21:170:37 | call to read | body | +| app.rb:247:17:247:33 | call to read | body | grapeRequestBodyString -| app.rb:252:17:252:35 | call to string | +| app.rb:252:17:252:35 | call to string | body | grapeRequestParams -| app.rb:257:19:257:32 | call to params | +| app.rb:257:19:257:32 | call to params | parameter | grapeRequestGet -| app.rb:262:18:262:28 | call to GET | +| app.rb:262:18:262:28 | call to GET | parameter | grapeRequestPost -| app.rb:267:19:267:30 | call to POST | +| app.rb:267:19:267:30 | call to POST | body | grapeRequestCookies -| app.rb:272:19:272:33 | call to cookies | +| app.rb:272:19:272:33 | call to cookies | cookie | grapeRequestEnv -| app.rb:277:16:277:26 | call to env | +| app.rb:277:16:277:26 | call to env | parameter | grapeRequestQueryString -| app.rb:282:10:282:29 | call to query_string | +| app.rb:282:10:282:29 | call to query_string | parameter | grapeRequestPathInfo -| app.rb:287:12:287:28 | call to path_info | +| app.rb:287:12:287:28 | call to path_info | url | diff --git a/ruby/ql/test/library-tests/frameworks/grape/Grape.ql b/ruby/ql/test/library-tests/frameworks/grape/Grape.ql index 09997b2c5b56..01d9b5095f53 100644 --- a/ruby/ql/test/library-tests/frameworks/grape/Grape.ql +++ b/ruby/ql/test/library-tests/frameworks/grape/Grape.ql @@ -23,20 +23,38 @@ query predicate grapeRouteParam(Grape::GrapeRouteParamSource routeParam) { any() query predicate grapeCookies(Grape::GrapeCookiesSource cookies) { any() } -query predicate grapeRequestBodyRead(Grape::GrapeRequestBodyReadSource src) { any() } +query predicate grapeRequestBodyRead(Grape::GrapeRequestBodyReadSource src, string kind) { + kind = src.getKind() +} -query predicate grapeRequestBodyString(Grape::GrapeRequestBodyStringSource src) { any() } +query predicate grapeRequestBodyString(Grape::GrapeRequestBodyStringSource src, string kind) { + kind = src.getKind() +} -query predicate grapeRequestParams(Grape::GrapeRequestParamsSource src) { any() } +query predicate grapeRequestParams(Grape::GrapeRequestParamsSource src, string kind) { + kind = src.getKind() +} -query predicate grapeRequestGet(Grape::GrapeRequestGetSource src) { any() } +query predicate grapeRequestGet(Grape::GrapeRequestGetSource src, string kind) { + kind = src.getKind() +} -query predicate grapeRequestPost(Grape::GrapeRequestPostSource src) { any() } +query predicate grapeRequestPost(Grape::GrapeRequestPostSource src, string kind) { + kind = src.getKind() +} -query predicate grapeRequestCookies(Grape::GrapeRequestCookiesSource src) { any() } +query predicate grapeRequestCookies(Grape::GrapeRequestCookiesSource src, string kind) { + kind = src.getKind() +} -query predicate grapeRequestEnv(Grape::GrapeRequestEnvSource src) { any() } +query predicate grapeRequestEnv(Grape::GrapeRequestEnvSource src, string kind) { + kind = src.getKind() +} -query predicate grapeRequestQueryString(Grape::GrapeRequestQueryStringSource src) { any() } +query predicate grapeRequestQueryString(Grape::GrapeRequestQueryStringSource src, string kind) { + kind = src.getKind() +} -query predicate grapeRequestPathInfo(Grape::GrapeRequestPathInfoSource src) { any() } +query predicate grapeRequestPathInfo(Grape::GrapeRequestPathInfoSource src, string kind) { + kind = src.getKind() +}