33 */
44
55import javascript
6+ import semmle.javascript.ViewComponentInput
67
78module Vue {
89 /** The global variable `Vue`, as an API graph entry point. */
@@ -85,17 +86,16 @@ module Vue {
8586 * A class with a `@Component` decorator, making it usable as an "options" object in Vue.
8687 */
8788 class ClassComponent extends DataFlow:: ClassNode {
89+ private ClassDefinition cls ;
8890 DataFlow:: Node decorator ;
8991
9092 ClassComponent ( ) {
91- exists ( ClassDefinition cls |
92- this = cls .flow ( ) and
93- cls .getADecorator ( ) .getExpression ( ) = decorator .asExpr ( ) and
94- (
95- componentDecorator ( ) .flowsTo ( decorator )
96- or
97- componentDecorator ( ) .getACall ( ) = decorator
98- )
93+ this = cls .flow ( ) and
94+ cls .getADecorator ( ) .getExpression ( ) = decorator .asExpr ( ) and
95+ (
96+ componentDecorator ( ) .flowsTo ( decorator )
97+ or
98+ componentDecorator ( ) .getACall ( ) = decorator
9999 )
100100 }
101101
@@ -105,6 +105,9 @@ module Vue {
105105 * These options correspond to the options one would pass to `new Vue({...})` or similar.
106106 */
107107 API:: Node getDecoratorOptions ( ) { result = decorator .( API:: CallNode ) .getParameter ( 0 ) }
108+
109+ /** Gets the AST node for the class definition. */
110+ ClassDefinition getClassDefinition ( ) { result = cls }
108111 }
109112
110113 private string memberKindVerb ( DataFlow:: MemberKind kind ) {
@@ -460,6 +463,12 @@ module Vue {
460463
461464 SingleFileComponent ( ) { this = MkSingleFileComponent ( file ) }
462465
466+ /** Gets a call to `defineProps` in this component. */
467+ DataFlow:: CallNode getDefinePropsCall ( ) {
468+ result = DataFlow:: globalVarRef ( "defineProps" ) .getACall ( ) and
469+ result .getFile ( ) = file
470+ }
471+
463472 override Template:: Element getTemplateElement ( ) {
464473 exists ( HTML:: Element e | result .( Template:: HtmlElement ) .getElement ( ) = e |
465474 e .getFile ( ) = file and
@@ -697,4 +706,68 @@ module Vue {
697706
698707 override ClientSideRemoteFlowKind getKind ( ) { result = kind }
699708 }
709+
710+ /**
711+ * Holds if the given type annotation indicates a value that is not typically considered taintable.
712+ */
713+ private predicate isSafeType ( TypeAnnotation type ) {
714+ type .isBooleany ( ) or
715+ type .isNumbery ( ) or
716+ type .isRawFunction ( ) or
717+ type instanceof FunctionTypeExpr
718+ }
719+
720+ /**
721+ * Holds if the given field has a type that indicates that is can not contain a taintable value.
722+ */
723+ private predicate isSafeField ( FieldDeclaration field ) { isSafeType ( field .getTypeAnnotation ( ) ) }
724+
725+ private DataFlow:: Node getPropSpec ( Component component ) {
726+ result = component .getOption ( "props" )
727+ or
728+ result = component .( SingleFileComponent ) .getDefinePropsCall ( ) .getArgument ( 0 )
729+ }
730+
731+ /**
732+ * Holds if `component` has an input prop with the given name, that is of a taintable type.
733+ */
734+ private predicate hasTaintableProp ( Component component , string name ) {
735+ exists ( DataFlow:: SourceNode spec | spec = getPropSpec ( component ) .getALocalSource ( ) |
736+ spec .( DataFlow:: ArrayCreationNode ) .getAnElement ( ) .getStringValue ( ) = name
737+ or
738+ exists ( DataFlow:: PropWrite write |
739+ write = spec .getAPropertyWrite ( name ) and
740+ not DataFlow:: globalVarRef ( [ "Number" , "Boolean" ] ) .flowsTo ( write .getRhs ( ) )
741+ )
742+ )
743+ or
744+ exists ( FieldDeclaration field |
745+ field = component .getAsClassComponent ( ) .getClassDefinition ( ) .getField ( name ) and
746+ DataFlow:: moduleMember ( "vue-property-decorator" , "Prop" )
747+ .getACall ( )
748+ .flowsToExpr ( field .getADecorator ( ) .getExpression ( ) ) and
749+ not isSafeField ( field )
750+ )
751+ or
752+ // defineProps() can be called with only type arguments and then the Vue compiler will
753+ // infer the prop types.
754+ exists ( CallExpr call , FieldDeclaration field |
755+ call = component .( SingleFileComponent ) .getDefinePropsCall ( ) .asExpr ( ) and
756+ field = call .getTypeArgument ( 0 ) .( InterfaceTypeExpr ) .getMember ( name ) and
757+ not isSafeField ( field )
758+ )
759+ }
760+
761+ private class PropAsViewComponentInput extends ViewComponentInput {
762+ PropAsViewComponentInput ( ) {
763+ exists ( Component component , string name | hasTaintableProp ( component , name ) |
764+ this = component .getAnInstanceRef ( ) .getAPropertyRead ( name )
765+ or
766+ // defineProps() returns the props
767+ this = component .( SingleFileComponent ) .getDefinePropsCall ( ) .getAPropertyRead ( name )
768+ )
769+ }
770+
771+ override string getSourceType ( ) { result = "Vue prop" }
772+ }
700773}
0 commit comments