mirror of
https://github.com/github/codeql.git
synced 2026-05-02 04:05:14 +02:00
Ruby: Add classes for detecting user input from graphql-ruby
This commit is contained in:
@@ -6,6 +6,7 @@ private import codeql.ruby.frameworks.ActionController
|
||||
private import codeql.ruby.frameworks.ActiveRecord
|
||||
private import codeql.ruby.frameworks.ActiveStorage
|
||||
private import codeql.ruby.frameworks.ActionView
|
||||
private import codeql.ruby.frameworks.GraphQL
|
||||
private import codeql.ruby.frameworks.Rails
|
||||
private import codeql.ruby.frameworks.StandardLibrary
|
||||
private import codeql.ruby.frameworks.Files
|
||||
|
||||
422
ruby/ql/lib/codeql/ruby/frameworks/GraphQL.qll
Normal file
422
ruby/ql/lib/codeql/ruby/frameworks/GraphQL.qll
Normal file
@@ -0,0 +1,422 @@
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.controlflow.CfgNodes
|
||||
private import codeql.ruby.DataFlow
|
||||
private import codeql.ruby.dataflow.RemoteFlowSources
|
||||
private import codeql.ruby.ast.internal.Module
|
||||
private import codeql.ruby.ApiGraphs
|
||||
|
||||
private class GraphqlRelayClassicMutationAccess extends ConstantReadAccess {
|
||||
//GraphQL::Schema::RelayClassicMutation
|
||||
GraphqlRelayClassicMutationAccess() {
|
||||
this =
|
||||
API::getTopLevelMember("GraphQL")
|
||||
.getMember("Schema")
|
||||
.getMember("RelayClassicMutation")
|
||||
.getAUse()
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
}
|
||||
}
|
||||
|
||||
private class GraphqlSchemaResolverAccess extends ConstantReadAccess {
|
||||
//GraphQL::Schema::Resolver
|
||||
GraphqlSchemaResolverAccess() {
|
||||
this =
|
||||
API::getTopLevelMember("GraphQL")
|
||||
.getMember("Schema")
|
||||
.getMember("Resolver")
|
||||
.getAUse()
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
}
|
||||
}
|
||||
|
||||
private class GraphqlSchemaObjectAccess extends ConstantReadAccess {
|
||||
//GraphQL::Schema::Object
|
||||
GraphqlSchemaObjectAccess() {
|
||||
this =
|
||||
API::getTopLevelMember("GraphQL")
|
||||
.getMember("Schema")
|
||||
.getMember("Object")
|
||||
.getAUse()
|
||||
.asExpr()
|
||||
.getExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `ClassDeclaration` for a class that extends `GraphQL::Schema::RelayClassicMutation`.
|
||||
* For example,
|
||||
*
|
||||
* ```rb
|
||||
* module Mutations
|
||||
* class BaseMutation < GraphQL::Schema::RelayClassicMutation
|
||||
* argument_class Types::BaseArgument
|
||||
* field_class Types::BaseField
|
||||
* input_object_class Types::BaseInputObject
|
||||
* object_class Types::BaseObject
|
||||
* end
|
||||
* end
|
||||
*
|
||||
* module Mutation
|
||||
* class MyMutation < BaseMutation
|
||||
* argument :something_id, ID, required: false
|
||||
* field :success, Boolean, null: false
|
||||
*
|
||||
* def resolve(something_id:)
|
||||
* # call your application logic here...
|
||||
* end
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
private class GraphqlRelayClassicMutationClass extends ClassDeclaration {
|
||||
GraphqlRelayClassicMutationClass() {
|
||||
// class BaseMutation < GraphQL::Schema::RelayClassicMutation
|
||||
this.getSuperclassExpr() instanceof GraphqlRelayClassicMutationAccess
|
||||
or
|
||||
// class MyMutation < BaseMutation
|
||||
exists(GraphqlRelayClassicMutationClass other |
|
||||
other.getModule() = resolveConstantReadAccess(this.getSuperclassExpr())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `ClassDeclaration` for a class that extends `GraphQL::Schema::Resolver`.
|
||||
* For example,
|
||||
*
|
||||
* ```rb
|
||||
* module Resolvers
|
||||
* class Base < GraphQL::Schema::Resolver
|
||||
* argument_class Arguments::Base
|
||||
* end
|
||||
* end
|
||||
*
|
||||
* module Resolvers
|
||||
* class RecommendedItems < Resolvers::Base
|
||||
* type [Types::Item], null: false
|
||||
* argument :order_by, Types::ItemOrder, required: false
|
||||
*
|
||||
* def resolve(order_by: )
|
||||
* # call your application logic here...
|
||||
* end
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
private class GraphqlSchemaResolverClass extends ClassDeclaration {
|
||||
GraphqlSchemaResolverClass() {
|
||||
// class BaseResolver < GraphQL::Schema::Resolver
|
||||
this.getSuperclassExpr() instanceof GraphqlSchemaResolverAccess
|
||||
or
|
||||
// class MyResolver < BaseResolver
|
||||
exists(GraphqlSchemaResolverClass other |
|
||||
other.getModule() = resolveConstantReadAccess(this.getSuperclassExpr())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `ClassDeclaration` for a class that extends `GraphQL::Schema::Object`.
|
||||
* For example,
|
||||
*
|
||||
* ```rb
|
||||
* class BaseObject < GraphQL::Schema::Object
|
||||
* field_class BaseField
|
||||
* end
|
||||
*
|
||||
* class Musician < BaseObject
|
||||
* field :favorite_key, Key
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class GraphqlSchemaObjectClass extends ClassDeclaration {
|
||||
GraphqlSchemaObjectClass() {
|
||||
// class BaseObject < GraphQL::Schema::Object
|
||||
this.getSuperclassExpr() instanceof GraphqlSchemaObjectAccess
|
||||
or
|
||||
// class MyObject < BaseObject
|
||||
exists(GraphqlSchemaObjectClass other |
|
||||
other.getModule() = resolveConstantReadAccess(this.getSuperclassExpr())
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a `GraphqlFieldDefinitionMethodCall` called in this class. */
|
||||
GraphqlFieldDefinitionMethodCall getAFieldDefinitionMethodCall() {
|
||||
result.getReceiverClass() = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `ClassDeclaration` for a class that extends either
|
||||
* `GraphQL::Schema::RelayClassicMutation` or
|
||||
* `GraphQL::Schema::Resolver`.
|
||||
*
|
||||
* Both of these classes have an overrideable `resolve` instance
|
||||
* method which can receive user input in order to resolve a query or mutation.
|
||||
*/
|
||||
private class GraphqlResolvableClass extends ClassDeclaration {
|
||||
GraphqlResolvableClass() {
|
||||
this instanceof GraphqlRelayClassicMutationClass or
|
||||
this instanceof GraphqlSchemaResolverClass
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `resolve` instance method on a sub-class of either
|
||||
* `GraphQL::Schema::RelayClassicMutation` or
|
||||
* `GraphQL::Schema::Resolver`.
|
||||
*
|
||||
* This `resolve` method is essentially an HTTP request handler.
|
||||
* The user input data comes in through a GraphQL query, is parsed by the GraphQL
|
||||
* library, and this method handles the request. Then the result is serialized
|
||||
* into a GraphQL response on the way out.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```rb
|
||||
* module Mutation
|
||||
* class NameAnInstrument < BaseMutationn
|
||||
* argument :instrument_uuid, Types::Uuid,
|
||||
* required: true,
|
||||
* loads: ::Instrument,
|
||||
* as: :instrument,
|
||||
* argument :name, String, required: true
|
||||
*
|
||||
* def load_instrument(uuid)
|
||||
* ::Instrument.find_by(uuid: uuid)
|
||||
* end
|
||||
*
|
||||
* # GraphqlResolveMethod
|
||||
* def resolve(instrument:, name:)
|
||||
* instrument.set_name(name)
|
||||
* end
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class GraphqlResolveMethod extends Method, HTTP::Server::RequestHandler::Range {
|
||||
private GraphqlResolvableClass resolvableClass;
|
||||
|
||||
GraphqlResolveMethod() { this = resolvableClass.getMethod("resolve") }
|
||||
|
||||
override Parameter getARoutedParameter() { result = this.getAParameter() }
|
||||
|
||||
override string getFramework() { result = "GraphQL" }
|
||||
|
||||
/** Gets the mutation class containing this method. */
|
||||
GraphqlResolvableClass getMutationClass() { result = resolvableClass }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `load_*` method on a sub-class of either
|
||||
* `GraphQL::Schema::RelayClassicMutation` or
|
||||
* `GraphQL::Schema::Resolver`.
|
||||
*
|
||||
* This method takes user input (some kind of ID or specifier) and is intended
|
||||
* to resolve the domain object using that ID.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```rb
|
||||
* module Mutation
|
||||
* class NameAnInstrument < BaseMutationn
|
||||
* argument :instrument_uuid, Types::Uuid,
|
||||
* required: true,
|
||||
* loads: ::Instrument,
|
||||
* as: :instrument,
|
||||
* argument :name, String, required: true
|
||||
*
|
||||
* # GraphqlLoadMethod
|
||||
* def load_instrument(uuid)
|
||||
* ::Instrument.find_by(uuid: uuid)
|
||||
* end
|
||||
*
|
||||
* def resolve(instrument:, name:)
|
||||
* instrument.set_name(name)
|
||||
* end
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class GraphqlLoadMethod extends Method, HTTP::Server::RequestHandler::Range {
|
||||
private GraphqlResolvableClass resolvableClass;
|
||||
|
||||
GraphqlLoadMethod() {
|
||||
this.getEnclosingModule() = resolvableClass and
|
||||
this.getName().regexpMatch("^load_.*")
|
||||
}
|
||||
|
||||
override Parameter getARoutedParameter() { result = this.getAParameter() }
|
||||
|
||||
override string getFramework() { result = "GraphQL" }
|
||||
|
||||
/** Gets the mutation class containing this method. */
|
||||
GraphqlResolvableClass getMutationClass() { result = resolvableClass }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `MethodCall` that represents calling a class method on a
|
||||
* a sub-class of `GraphQL::Schema::Object`
|
||||
*/
|
||||
private class GraphqlSchemaObjectClassMethodCall extends MethodCall {
|
||||
private GraphqlSchemaObjectClass recvCls;
|
||||
|
||||
GraphqlSchemaObjectClassMethodCall() {
|
||||
// e.g. Foo.some_method(...)
|
||||
recvCls.getModule() = resolveConstantReadAccess(this.getReceiver())
|
||||
or
|
||||
// e.g. self.some_method(...) within a graphql Object or Interface
|
||||
this.getReceiver() instanceof Self and
|
||||
this.getEnclosingModule() = recvCls
|
||||
}
|
||||
|
||||
/** Gets the `GraphqlSchemaObjectClass` representing the receiver of this method. */
|
||||
GraphqlSchemaObjectClass getReceiverClass() { result = recvCls }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `MethodCall` that represents calling the class method `field` on a GraphQL
|
||||
* object.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```rb
|
||||
* class Types::User < GraphQL::Schema::Object
|
||||
* # GraphqlFieldDefinitionMethodCall
|
||||
* field :email, String
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* See also: https://graphql-ruby.org/fields/introduction.html
|
||||
*/
|
||||
class GraphqlFieldDefinitionMethodCall extends GraphqlSchemaObjectClassMethodCall {
|
||||
GraphqlFieldDefinitionMethodCall() { this.getMethodName() = "field" }
|
||||
|
||||
/** Gets the name of this GraphQL field. */
|
||||
string getFieldName() { result = this.getArgument(0).getValueText() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `MethodCall` that represents calling the class method `argument` inside the
|
||||
* block for a `field` definition on a GraphQL object.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```rb
|
||||
* class Types::User < GraphQL::Schema::Object
|
||||
* field :email, String
|
||||
* field :friends, [Types::User] do
|
||||
* # GraphqlFieldArgumentDefinitionMethodCall
|
||||
* argument :starts_with, String, "Show only friends matching the given prefix"
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* See Also: https://graphql-ruby.org/fields/arguments
|
||||
*/
|
||||
private class GraphqlFieldArgumentDefinitionMethodCall extends GraphqlSchemaObjectClassMethodCall {
|
||||
private GraphqlFieldDefinitionMethodCall fieldDefinition;
|
||||
|
||||
GraphqlFieldArgumentDefinitionMethodCall() {
|
||||
this.getMethodName() = "argument" and
|
||||
fieldDefinition.getBlock() = this.getEnclosingCallable()
|
||||
}
|
||||
|
||||
/** Gets the method call that defines the GraphQL field this argument is for */
|
||||
GraphqlFieldDefinitionMethodCall getFieldDefinition() { result = fieldDefinition }
|
||||
|
||||
/** Gets the name of the field which this is an argument for */
|
||||
string getFieldName() { result = this.getFieldDefinition().getFieldName() }
|
||||
|
||||
/** Gets the name of the argument (i.e. the first argument to this `argument` method call) */
|
||||
string getArgumentName() { result = this.getArgument(0).(SymbolLiteral).getValueText() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Method` which represents an instance method which is the resolver method for a
|
||||
* GraphQL `field`.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```rb
|
||||
* class Types::User < GraphQL::Schema::Object
|
||||
* field :email, String
|
||||
* field :friends, [Types::User] do
|
||||
* argument :starts_with, String, "Show only friends matching the given prefix"
|
||||
* end
|
||||
*
|
||||
* # GraphqlFieldResolutionMethod
|
||||
* def friends(starts_with:)
|
||||
* object.friends.where("name like #{starts_with}")
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* or:
|
||||
*
|
||||
* ```rb
|
||||
* class Types::User < GraphQL::Schema::Object
|
||||
* field :email, String
|
||||
* field :friends, [Types::User] do
|
||||
* argument :starts_with, String, "Show only friends matching the given prefix",
|
||||
* resolver_method: :my_custom_method, extras: [:graphql_name]
|
||||
* end
|
||||
*
|
||||
* # GraphqlFieldResolutionMethod
|
||||
* def my_custom_method(**args)
|
||||
* puts args[:graphql_name] # for debugging
|
||||
* object.friends.where("name like #{args[:starts_with]}")
|
||||
* end
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
class GraphqlFieldResolutionMethod extends Method, HTTP::Server::RequestHandler::Range {
|
||||
private GraphqlSchemaObjectClass schemaObjectClass;
|
||||
|
||||
GraphqlFieldResolutionMethod() {
|
||||
this.getEnclosingModule() = schemaObjectClass and
|
||||
exists(GraphqlFieldDefinitionMethodCall defn |
|
||||
// field :foo, resolver_method: :custom_method
|
||||
// def custom_method(...)
|
||||
defn.getKeywordArgument("resolver_method").(SymbolLiteral).getValueText() = this.getName()
|
||||
or
|
||||
// field :foo
|
||||
// def foo(...)
|
||||
not exists(defn.getKeywordArgument("resolver_method").(SymbolLiteral)) and
|
||||
defn.getFieldName() = this.getName()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the method call which is the definition of the field corresponding to this resolver method. */
|
||||
GraphqlFieldDefinitionMethodCall getDefinition() {
|
||||
result.getKeywordArgument("resolver_method").(SymbolLiteral).getValueText() = this.getName()
|
||||
or
|
||||
not exists(result.getKeywordArgument("resolver_method").(SymbolLiteral)) and
|
||||
result.getFieldName() = this.getName()
|
||||
}
|
||||
|
||||
// check for a named argument the same name as a defined argument for this field
|
||||
override Parameter getARoutedParameter() {
|
||||
result = this.getAParameter() and
|
||||
exists(GraphqlFieldArgumentDefinitionMethodCall argDefn |
|
||||
argDefn.getEnclosingCallable() = this.getDefinition().getBlock() and
|
||||
(
|
||||
result.(KeywordParameter).hasName(argDefn.getArgumentName())
|
||||
or
|
||||
// TODO this will cause false positives because now *anything* in the **args
|
||||
// param will be flagged as as RoutedParameter/RemoteFlowSource, but really
|
||||
// only the hash keys corresponding to the defined arguments are user input
|
||||
// others could be things defined in the `:extras` keyword argument to the `argument`
|
||||
result instanceof HashSplatParameter // often you see `def field(**args)`
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override string getFramework() { result = "GraphQL" }
|
||||
|
||||
/** Gets the class containing this method. */
|
||||
GraphqlSchemaObjectClass getGraphqlClass() { result = schemaObjectClass }
|
||||
}
|
||||
46
ruby/ql/test/library-tests/frameworks/GraphQL.expected
Normal file
46
ruby/ql/test/library-tests/frameworks/GraphQL.expected
Normal file
@@ -0,0 +1,46 @@
|
||||
graphqlSchemaObjectClass
|
||||
| app/graphql/types/base_object.rb:2:3:4:5 | BaseObject |
|
||||
| app/graphql/types/mutation_type.rb:2:3:4:5 | MutationType |
|
||||
| app/graphql/types/query_type.rb:2:3:45:5 | QueryType |
|
||||
graphqlSchemaObjectFieldDefinition
|
||||
| app/graphql/types/mutation_type.rb:2:3:4:5 | MutationType | app/graphql/types/mutation_type.rb:3:5:3:44 | call to field |
|
||||
| app/graphql/types/query_type.rb:2:3:45:5 | QueryType | app/graphql/types/query_type.rb:3:5:5:40 | call to field |
|
||||
| app/graphql/types/query_type.rb:2:3:45:5 | QueryType | app/graphql/types/query_type.rb:7:5:9:7 | call to field |
|
||||
| app/graphql/types/query_type.rb:2:3:45:5 | QueryType | app/graphql/types/query_type.rb:15:5:17:7 | call to field |
|
||||
| app/graphql/types/query_type.rb:2:3:45:5 | QueryType | app/graphql/types/query_type.rb:24:5:26:7 | call to field |
|
||||
| app/graphql/types/query_type.rb:2:3:45:5 | QueryType | app/graphql/types/query_type.rb:32:5:35:7 | call to field |
|
||||
graphqlResolveMethod
|
||||
| app/graphql/mutations/dummy.rb:9:5:12:7 | resolve |
|
||||
| app/graphql/resolvers/dummy_resolver.rb:10:5:13:7 | resolve |
|
||||
graphqlResolveMethodRoutedParameter
|
||||
| app/graphql/mutations/dummy.rb:9:5:12:7 | resolve | app/graphql/mutations/dummy.rb:9:17:9:25 | something |
|
||||
| app/graphql/resolvers/dummy_resolver.rb:10:5:13:7 | resolve | app/graphql/resolvers/dummy_resolver.rb:10:17:10:25 | something |
|
||||
graphqlLoadMethod
|
||||
| app/graphql/mutations/dummy.rb:5:5:7:7 | load_something |
|
||||
| app/graphql/resolvers/dummy_resolver.rb:6:5:8:7 | load_something |
|
||||
graphqlLoadMethodRoutedParameter
|
||||
| app/graphql/mutations/dummy.rb:5:5:7:7 | load_something | app/graphql/mutations/dummy.rb:5:24:5:25 | id |
|
||||
| app/graphql/resolvers/dummy_resolver.rb:6:5:8:7 | load_something | app/graphql/resolvers/dummy_resolver.rb:6:24:6:25 | id |
|
||||
graphqlFieldDefinitionMethodCall
|
||||
| app/graphql/types/mutation_type.rb:3:5:3:44 | call to field |
|
||||
| app/graphql/types/query_type.rb:3:5:5:40 | call to field |
|
||||
| app/graphql/types/query_type.rb:7:5:9:7 | call to field |
|
||||
| app/graphql/types/query_type.rb:15:5:17:7 | call to field |
|
||||
| app/graphql/types/query_type.rb:24:5:26:7 | call to field |
|
||||
| app/graphql/types/query_type.rb:32:5:35:7 | call to field |
|
||||
graphqlFieldResolutionMethod
|
||||
| app/graphql/types/query_type.rb:10:5:13:7 | with_arg |
|
||||
| app/graphql/types/query_type.rb:18:5:22:7 | custom_method |
|
||||
| app/graphql/types/query_type.rb:27:5:30:7 | with_splat |
|
||||
| app/graphql/types/query_type.rb:36:5:40:7 | with_splat_and_named_arg |
|
||||
graphqlFieldResolutionRoutedParameter
|
||||
| app/graphql/types/query_type.rb:10:5:13:7 | with_arg | app/graphql/types/query_type.rb:10:18:10:23 | number |
|
||||
| app/graphql/types/query_type.rb:18:5:22:7 | custom_method | app/graphql/types/query_type.rb:18:23:18:33 | blah_number |
|
||||
| app/graphql/types/query_type.rb:27:5:30:7 | with_splat | app/graphql/types/query_type.rb:27:20:27:25 | **args |
|
||||
| app/graphql/types/query_type.rb:36:5:40:7 | with_splat_and_named_arg | app/graphql/types/query_type.rb:36:34:36:37 | arg1 |
|
||||
| app/graphql/types/query_type.rb:36:5:40:7 | with_splat_and_named_arg | app/graphql/types/query_type.rb:36:41:36:46 | **rest |
|
||||
graphqlFieldResolutionDefinition
|
||||
| app/graphql/types/query_type.rb:10:5:13:7 | with_arg | app/graphql/types/query_type.rb:7:5:9:7 | call to field |
|
||||
| app/graphql/types/query_type.rb:18:5:22:7 | custom_method | app/graphql/types/query_type.rb:15:5:17:7 | call to field |
|
||||
| app/graphql/types/query_type.rb:27:5:30:7 | with_splat | app/graphql/types/query_type.rb:24:5:26:7 | call to field |
|
||||
| app/graphql/types/query_type.rb:36:5:40:7 | with_splat_and_named_arg | app/graphql/types/query_type.rb:32:5:35:7 | call to field |
|
||||
36
ruby/ql/test/library-tests/frameworks/GraphQL.ql
Normal file
36
ruby/ql/test/library-tests/frameworks/GraphQL.ql
Normal file
@@ -0,0 +1,36 @@
|
||||
private import codeql.ruby.frameworks.GraphQL
|
||||
private import codeql.ruby.AST
|
||||
|
||||
query predicate graphqlSchemaObjectClass(GraphqlSchemaObjectClass cls) { any() }
|
||||
|
||||
query predicate graphqlSchemaObjectFieldDefinition(
|
||||
GraphqlSchemaObjectClass cls, GraphqlFieldDefinitionMethodCall meth
|
||||
) {
|
||||
cls.getAFieldDefinitionMethodCall() = meth
|
||||
}
|
||||
|
||||
query predicate graphqlResolveMethod(GraphqlResolveMethod meth) { any() }
|
||||
|
||||
query predicate graphqlResolveMethodRoutedParameter(GraphqlResolveMethod meth, Parameter p) {
|
||||
meth.getARoutedParameter() = p
|
||||
}
|
||||
|
||||
query predicate graphqlLoadMethod(GraphqlLoadMethod meth) { any() }
|
||||
|
||||
query predicate graphqlLoadMethodRoutedParameter(GraphqlLoadMethod meth, Parameter p) {
|
||||
meth.getARoutedParameter() = p
|
||||
}
|
||||
|
||||
query predicate graphqlFieldDefinitionMethodCall(GraphqlFieldDefinitionMethodCall cls) { any() }
|
||||
|
||||
query predicate graphqlFieldResolutionMethod(GraphqlFieldResolutionMethod cls) { any() }
|
||||
|
||||
query predicate graphqlFieldResolutionRoutedParameter(GraphqlFieldResolutionMethod meth, Parameter p) {
|
||||
meth.getARoutedParameter() = p
|
||||
}
|
||||
|
||||
query predicate graphqlFieldResolutionDefinition(
|
||||
GraphqlFieldResolutionMethod meth, GraphqlFieldDefinitionMethodCall def
|
||||
) {
|
||||
meth.getDefinition() = def
|
||||
}
|
||||
@@ -19,6 +19,15 @@ kernelSystemCallExecutions
|
||||
| CommandExecution.rb:19:1:19:59 | call to system |
|
||||
| CommandExecution.rb:20:1:20:62 | call to system |
|
||||
| CommandExecution.rb:21:1:21:72 | call to system |
|
||||
| app/graphql/mutations/dummy.rb:10:7:10:33 | call to system |
|
||||
| app/graphql/resolvers/dummy_resolver.rb:11:7:11:33 | call to system |
|
||||
| app/graphql/types/query_type.rb:11:7:11:30 | call to system |
|
||||
| app/graphql/types/query_type.rb:19:7:19:35 | call to system |
|
||||
| app/graphql/types/query_type.rb:20:7:20:30 | call to system |
|
||||
| app/graphql/types/query_type.rb:28:7:28:40 | call to system |
|
||||
| app/graphql/types/query_type.rb:37:7:37:28 | call to system |
|
||||
| app/graphql/types/query_type.rb:38:7:38:35 | call to system |
|
||||
| app/graphql/types/query_type.rb:43:7:43:27 | call to system |
|
||||
kernelExecCallExecutions
|
||||
| CommandExecution.rb:23:1:23:16 | call to exec |
|
||||
| CommandExecution.rb:24:1:24:19 | call to exec |
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
module Mutations
|
||||
class BaseMutation < GraphQL::Schema::RelayClassicMutation
|
||||
argument_class Types::BaseArgument
|
||||
field_class Types::BaseField
|
||||
input_object_class Types::BaseInputObject
|
||||
object_class Types::BaseObject
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
module Mutations
|
||||
class Dummy < BaseMutation
|
||||
argument :something_id, ID, required: false
|
||||
|
||||
def load_something(id)
|
||||
"Something number #{id}"
|
||||
end
|
||||
|
||||
def resolve(something:)
|
||||
system("echo #{something}")
|
||||
{ success: true }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
module Resolvers
|
||||
class Base < GraphQL::Schema::Resolver
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
module Resolvers
|
||||
class DummyResolver < Resolvers::Base
|
||||
type String, null: false
|
||||
argument :something_id, ID, required: true
|
||||
|
||||
def load_something(id)
|
||||
"Something number #{id}"
|
||||
end
|
||||
|
||||
def resolve(something:)
|
||||
system("echo #{something}")
|
||||
"true"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
module Types
|
||||
class BaseArgument < GraphQL::Schema::Argument
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
module Types
|
||||
class BaseField < GraphQL::Schema::Field
|
||||
argument_class Types::BaseArgument
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
module Types
|
||||
class BaseInputObject < GraphQL::Schema::InputObject
|
||||
argument_class Types::BaseArgument
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
module Types
|
||||
module BaseInterface
|
||||
include GraphQL::Schema::Interface
|
||||
|
||||
field_class Types::BaseField
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
module Types
|
||||
class BaseObject < GraphQL::Schema::Object
|
||||
field_class Types::BaseField
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
module Types
|
||||
class MutationType < Types::BaseObject
|
||||
field :dummy, mutation: Mutations::Dummy
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
module Types
|
||||
class QueryType < Types::BaseObject
|
||||
field :test_field, String, null: false,
|
||||
description: "An example field added by the generator",
|
||||
resolver: Resolvers::DummyResolver
|
||||
|
||||
field :with_arg, String, null: false, description: "A field with an argument" do
|
||||
argument :number, Int, "A number", required: true
|
||||
end
|
||||
def with_arg(number:)
|
||||
system("echo #{number}")
|
||||
number.to_s
|
||||
end
|
||||
|
||||
field :with_method, String, null: false, description: "A field with a custom resolver method", resolver_method: :custom_method do
|
||||
argument :blah_number, Int, "A number", required: true
|
||||
end
|
||||
def custom_method(blah_number:, number: nil)
|
||||
system("echo #{blah_number}")
|
||||
system("echo #{number}")
|
||||
blah_number.to_s
|
||||
end
|
||||
|
||||
field :with_splat, String, null: false, description: "A field with a double-splatted argument" do
|
||||
argument :something, Int, "A number", required: true
|
||||
end
|
||||
def with_splat(**args)
|
||||
system("echo #{args[:something]}")
|
||||
args[:something].to_s
|
||||
end
|
||||
|
||||
field :with_splat_and_named_arg, String, null: false, description: "A field with two named arguments, where the method captures the second via a hash splat param" do
|
||||
argument :arg1, Int, "A number", required: true
|
||||
argument :arg2, Int, "Another number", required: true
|
||||
end
|
||||
def with_splat_and_named_arg(arg1:, **rest)
|
||||
system("echo #{arg1}")
|
||||
system("echo #{rest[:arg2]}")
|
||||
arg1.to_s
|
||||
end
|
||||
|
||||
def foo(arg)
|
||||
system("echo #{arg}")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -10,6 +10,8 @@ edges
|
||||
| CommandInjection.rb:6:15:6:26 | ...[...] : | CommandInjection.rb:34:39:34:51 | "grep #{...}" |
|
||||
| CommandInjection.rb:46:15:46:20 | call to params : | CommandInjection.rb:46:15:46:26 | ...[...] : |
|
||||
| CommandInjection.rb:46:15:46:26 | ...[...] : | CommandInjection.rb:50:24:50:36 | "echo #{...}" |
|
||||
| CommandInjection.rb:64:18:64:23 | number : | CommandInjection.rb:65:14:65:29 | "echo #{...}" |
|
||||
| CommandInjection.rb:72:23:72:33 | blah_number : | CommandInjection.rb:73:14:73:34 | "echo #{...}" |
|
||||
nodes
|
||||
| CommandInjection.rb:6:15:6:20 | call to params : | semmle.label | call to params : |
|
||||
| CommandInjection.rb:6:15:6:26 | ...[...] : | semmle.label | ...[...] : |
|
||||
@@ -24,6 +26,10 @@ nodes
|
||||
| CommandInjection.rb:46:15:46:20 | call to params : | semmle.label | call to params : |
|
||||
| CommandInjection.rb:46:15:46:26 | ...[...] : | semmle.label | ...[...] : |
|
||||
| CommandInjection.rb:50:24:50:36 | "echo #{...}" | semmle.label | "echo #{...}" |
|
||||
| CommandInjection.rb:64:18:64:23 | number : | semmle.label | number : |
|
||||
| CommandInjection.rb:65:14:65:29 | "echo #{...}" | semmle.label | "echo #{...}" |
|
||||
| CommandInjection.rb:72:23:72:33 | blah_number : | semmle.label | blah_number : |
|
||||
| CommandInjection.rb:73:14:73:34 | "echo #{...}" | semmle.label | "echo #{...}" |
|
||||
subpaths
|
||||
#select
|
||||
| CommandInjection.rb:7:10:7:15 | #{...} | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:7:10:7:15 | #{...} | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
|
||||
@@ -35,3 +41,5 @@ subpaths
|
||||
| CommandInjection.rb:33:24:33:36 | "echo #{...}" | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:33:24:33:36 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
|
||||
| CommandInjection.rb:34:39:34:51 | "grep #{...}" | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:34:39:34:51 | "grep #{...}" | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
|
||||
| CommandInjection.rb:50:24:50:36 | "echo #{...}" | CommandInjection.rb:46:15:46:20 | call to params : | CommandInjection.rb:50:24:50:36 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:46:15:46:20 | call to params | a user-provided value |
|
||||
| CommandInjection.rb:65:14:65:29 | "echo #{...}" | CommandInjection.rb:64:18:64:23 | number : | CommandInjection.rb:65:14:65:29 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:64:18:64:23 | number | a user-provided value |
|
||||
| CommandInjection.rb:73:14:73:34 | "echo #{...}" | CommandInjection.rb:72:23:72:33 | blah_number : | CommandInjection.rb:73:14:73:34 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:72:23:72:33 | blah_number | a user-provided value |
|
||||
|
||||
@@ -50,3 +50,41 @@ EOF
|
||||
Open3.capture2("echo #{cmd}")
|
||||
end
|
||||
end
|
||||
|
||||
module Types
|
||||
class BaseObject < GraphQL::Schema::Object; end
|
||||
class QueryType < BaseObject
|
||||
field :test_field, String, null: false,
|
||||
description: "An example field added by the generator",
|
||||
resolver: Resolvers::DummyResolver
|
||||
|
||||
field :with_arg, String, null: false, description: "A field with an argument" do
|
||||
argument :number, Int, "A number", required: true
|
||||
end
|
||||
def with_arg(number:)
|
||||
system("echo #{number}")
|
||||
number.to_s
|
||||
end
|
||||
|
||||
field :with_method, String, null: false, description: "A field with a custom resolver method", resolver_method: :custom_method do
|
||||
argument :blah_number, Int, "A number", required: true
|
||||
end
|
||||
def custom_method(blah_number:, number: nil)
|
||||
system("echo #{blah_number}")
|
||||
system("echo #{number}") # OK, number: is not an `argument` for this field
|
||||
blah_number.to_s
|
||||
end
|
||||
|
||||
field :with_splat, String, null: false, description: "A field with a double-splatted argument" do
|
||||
argument :something, Int, "A number", required: true
|
||||
end
|
||||
def with_splat(**args)
|
||||
system("echo #{args[:something]}")
|
||||
args[:something].to_s
|
||||
end
|
||||
|
||||
def foo(arg)
|
||||
system("echo #{arg}") # OK, this is just a random method, not a resolver method
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user