@@ -253,6 +253,12 @@ class GraphqlFieldDefinitionMethodCall extends GraphqlSchemaObjectClassMethodCal
253253
254254 /** Gets the name of this GraphQL field. */
255255 string getFieldName ( ) { result = this .getArgument ( 0 ) .getConstantValue ( ) .getStringlikeValue ( ) }
256+
257+ GraphqlType getFieldType ( ) { result = this .getArgument ( 1 ) }
258+
259+ GraphqlFieldArgumentDefinitionMethodCall getArgumentCall ( ) {
260+ result .getEnclosingCallable ( ) = this .getBlock ( )
261+ }
256262}
257263
258264/**
@@ -289,6 +295,38 @@ private class GraphqlFieldArgumentDefinitionMethodCall extends GraphqlSchemaObje
289295
290296 /** Gets the name of the argument (i.e. the first argument to this `argument` method call) */
291297 string getArgumentName ( ) { result = this .getArgument ( 0 ) .getConstantValue ( ) .getStringlikeValue ( ) }
298+
299+ /** Gets the type of this argument */
300+ GraphqlType getArgumentType ( ) { result = this .getArgument ( 1 ) }
301+ }
302+
303+ private DataFlow:: LocalSourceNode graphQlEnum ( ) {
304+ result =
305+ API:: getTopLevelMember ( "GraphQL" )
306+ .getMember ( "Schema" )
307+ .getMember ( "Enum" )
308+ .getADescendentModule ( )
309+ .getAnImmediateReference ( )
310+ }
311+
312+ private class GraphqlType extends ConstantAccess {
313+ Module getModule ( ) { result .getAnImmediateReference ( ) = this }
314+
315+ GraphqlType getAField ( ) { result = this .getField ( _) }
316+
317+ GraphqlType getField ( string name ) {
318+ result =
319+ any ( GraphqlFieldDefinitionMethodCall field |
320+ field .getFieldName ( ) = name and
321+ this .getModule ( ) .getADeclaration ( ) = field .getReceiverClass ( )
322+ ) .getFieldType ( )
323+ }
324+
325+ predicate isEnum ( ) { graphQlEnum ( ) .asExpr ( ) .getExpr ( ) = this }
326+
327+ predicate isUserControlled ( ) { this .getName ( ) = [ "String" , "ID" , "JSON" ] }
328+
329+ predicate isScalar ( ) { not exists ( this .getAField ( ) ) and not this .isEnum ( ) }
292330}
293331
294332/**
@@ -363,16 +401,9 @@ class GraphqlFieldResolutionMethod extends Method, Http::Server::RequestHandler:
363401 override Parameter getARoutedParameter ( ) {
364402 result = this .getAParameter ( ) and
365403 exists ( GraphqlFieldArgumentDefinitionMethodCall argDefn |
366- argDefn .getEnclosingCallable ( ) = this .getDefinition ( ) .getBlock ( ) and
367- (
368- result .( KeywordParameter ) .hasName ( argDefn .getArgumentName ( ) )
369- or
370- // TODO this will cause false positives because now *anything* in the **args
371- // param will be flagged as RoutedParameter/RemoteFlowSource, but really
372- // only the hash keys corresponding to the defined arguments are user input
373- // others could be things defined in the `:extras` keyword argument to the `argument`
374- result instanceof HashSplatParameter // often you see `def field(**args)`
375- )
404+ argDefn = this .getDefinition ( ) .getArgumentCall ( )
405+ |
406+ result .( KeywordParameter ) .hasName ( argDefn .getArgumentName ( ) )
376407 )
377408 }
378409
@@ -383,3 +414,43 @@ class GraphqlFieldResolutionMethod extends Method, Http::Server::RequestHandler:
383414 /** Gets the class containing this method. */
384415 GraphqlSchemaObjectClass getGraphqlClass ( ) { result = schemaObjectClass }
385416}
417+
418+ private DataFlow:: CallNode hashAccess ( DataFlow:: Node recv , string key ) {
419+ result .asExpr ( ) instanceof ExprNodes:: ElementReferenceCfgNode and
420+ result .getArgument ( 0 ) .getConstantValue ( ) .isStringlikeValue ( key ) and
421+ result .getReceiver ( ) = recv
422+ }
423+
424+ private DataFlow:: CallNode parameterAccess (
425+ GraphqlFieldResolutionMethod method , GraphqlFieldArgumentDefinitionMethodCall def ,
426+ HashSplatParameter param , string key , GraphqlType type
427+ ) {
428+ param = method .getARoutedParameter ( ) and
429+ def = method .getDefinition ( ) .getArgumentCall ( ) and
430+ (
431+ // Direct access to the params hash
432+ def .getArgumentType ( ) = type and
433+ def .getArgumentName ( ) = key and
434+ exists ( DataFlow:: Node paramRead |
435+ paramRead .asExpr ( ) .getExpr ( ) = param .getVariable ( ) .getAnAccess ( ) .( VariableReadAccess ) and
436+ result = hashAccess ( paramRead , key )
437+ )
438+ or
439+ // Nested access
440+ exists ( GraphqlType type2 |
441+ parameterAccess ( method , _, param , _, type2 )
442+ .( DataFlow:: LocalSourceNode )
443+ .flowsTo ( result .getReceiver ( ) ) and
444+ result = hashAccess ( _, key ) and
445+ type2 .getField ( key ) = type
446+ )
447+ )
448+ }
449+
450+ private class GraphqlParameterAccess extends RemoteFlowSource:: Range {
451+ GraphqlParameterAccess ( ) {
452+ exists ( GraphqlType type | this = parameterAccess ( _, _, _, _, type ) and type .isScalar ( ) )
453+ }
454+
455+ override string getSourceType ( ) { result = "GraphQL" }
456+ }
0 commit comments