44
55import javascript
66import semmle.javascript.security.dataflow.RequestForgeryCustomizations
7- import semmle.javascript.security.dataflow.UrlConcatenation
87
98/**
109 * Provide model for [Execa](https://github.com/sindresorhus/execa) package
1110 */
1211module Execa {
1312 /**
14- * The Execa input file option
13+ * The Execa input file read and output file write
1514 */
16- class ExecaRead extends FileSystemReadAccess , DataFlow:: Node {
17- API:: Node execaNode ;
15+ class ExecaFileSystemAccess extends FileSystemReadAccess , DataFlow:: Node {
16+ API:: Node execaArg ;
17+ boolean isPipedToFile ;
1818
19- ExecaRead ( ) {
19+ ExecaFileSystemAccess ( ) {
2020 (
21- execaNode = API:: moduleImport ( "execa" ) .getMember ( "$" ) .getParameter ( 0 )
21+ execaArg = API:: moduleImport ( "execa" ) .getMember ( "$" ) .getParameter ( 0 ) and
22+ isPipedToFile = false
2223 or
23- execaNode =
24+ execaArg =
2425 API:: moduleImport ( "execa" )
2526 .getMember ( [ "execa" , "execaCommand" , "execaCommandSync" , "execaSync" ] )
26- .getParameter ( [ 0 , 1 , 2 ] )
27+ .getParameter ( [ 0 , 1 , 2 ] ) and
28+ isPipedToFile = false
29+ or
30+ execaArg =
31+ API:: moduleImport ( "execa" )
32+ .getMember ( [ "execa" , "execaCommand" , "execaCommandSync" , "execaSync" ] )
33+ .getReturn ( )
34+ .getMember ( [ "pipeStdout" , "pipeAll" , "pipeStderr" ] )
35+ .getParameter ( 0 ) and
36+ isPipedToFile = true
2737 ) and
28- this = execaNode .asSink ( )
38+ this = execaArg .asSink ( )
2939 }
3040
31- // data is the output of a command so IDK how it can be implemented
3241 override DataFlow:: Node getADataNode ( ) { none ( ) }
3342
3443 override DataFlow:: Node getAPathArgument ( ) {
35- result = execaNode .getMember ( "inputFile" ) .asSink ( )
44+ result = execaArg .getMember ( "inputFile" ) .asSink ( ) and isPipedToFile = false
45+ or
46+ result = execaArg .asSink ( ) and isPipedToFile = true
3647 }
3748 }
3849
3950 /**
4051 * A call to `execa.execa` or `execa.execaSync`
4152 */
4253 class ExecaCall extends API:: CallNode {
43- string name ;
54+ boolean isSync ;
4455
4556 ExecaCall ( ) {
4657 this = API:: moduleImport ( "execa" ) .getMember ( "execa" ) .getACall ( ) and
47- name = "execa"
58+ isSync = false
4859 or
4960 this = API:: moduleImport ( "execa" ) .getMember ( "execaSync" ) .getACall ( ) and
50- name = "execaSync"
61+ isSync = true
5162 }
52-
53- /** Gets the name of the exported function, such as `rm` in `shelljs.rm()`. */
54- string getName ( ) { result = name }
5563 }
5664
5765 /**
5866 * The system command execution nodes for `execa.execa` or `execa.execaSync` functions
5967 */
6068 class ExecaExec extends SystemCommandExecution , ExecaCall {
61- ExecaExec ( ) { name = [ "execa" , "execaSync" ] }
69+ ExecaExec ( ) { isSync = [ false , true ] }
6270
6371 override DataFlow:: Node getACommandArgument ( ) { result = this .getArgument ( 0 ) }
6472
@@ -73,7 +81,15 @@ module Execa {
7381 isExecaShellEnable ( this .getParameter ( 1 ) )
7482 }
7583
76- override predicate isSync ( ) { name = "execaSync" }
84+ override DataFlow:: Node getArgumentList ( ) {
85+ // execa(cmd, [arg]);
86+ exists ( DataFlow:: Node arg | arg = this .getArgument ( 1 ) |
87+ // if it is a object then it is a option argument not command argument
88+ result = arg and not arg .asExpr ( ) instanceof ObjectExpr
89+ )
90+ }
91+
92+ override predicate isSync ( ) { isSync = true }
7793
7894 override DataFlow:: Node getOptionsArg ( ) {
7995 result = this .getLastArgument ( ) and result .asExpr ( ) instanceof ObjectExpr
@@ -84,116 +100,97 @@ module Execa {
84100 * A call to `execa.$` or `execa.$.sync` tag functions
85101 */
86102 private class ExecaScriptExpr extends DataFlow:: ExprNode {
87- string name ;
103+ boolean isSync ;
88104
89105 ExecaScriptExpr ( ) {
90106 this .asExpr ( ) =
91107 [
92108 API:: moduleImport ( "execa" ) .getMember ( "$" ) ,
93109 API:: moduleImport ( "execa" ) .getMember ( "$" ) .getReturn ( )
94110 ] .getAValueReachableFromSource ( ) .asExpr ( ) and
95- name = "ASync"
111+ isSync = false
96112 or
97113 this .asExpr ( ) =
98114 [
99115 API:: moduleImport ( "execa" ) .getMember ( "$" ) .getMember ( "sync" ) ,
100116 API:: moduleImport ( "execa" ) .getMember ( "$" ) .getMember ( "sync" ) .getReturn ( )
101117 ] .getAValueReachableFromSource ( ) .asExpr ( ) and
102- name = "Sync"
118+ isSync = true
103119 }
104-
105- /** Gets the name of the exported function, such as `rm` in `shelljs.rm()`. */
106- string getName ( ) { result = name }
107120 }
108121
109122 /**
110123 * The system command execution nodes for `execa.$` or `execa.$.sync` tag functions
111124 */
112125 class ExecaScriptEec extends SystemCommandExecution , ExecaScriptExpr {
113- ExecaScriptEec ( ) { name = [ "Sync" , "ASync" ] }
126+ ExecaScriptEec ( ) { isSync = [ false , true ] }
114127
115128 override DataFlow:: Node getACommandArgument ( ) {
116- result .asExpr ( ) = templateLiteralChildAsSink ( this .asExpr ( ) ) .getChildExpr ( 0 )
129+ exists ( TemplateLiteral tl | isFirstTaggedTemplateParameter ( this .asExpr ( ) , tl ) |
130+ result .asExpr ( ) = tl .getChildExpr ( 0 ) and
131+ not result .asExpr ( ) .mayHaveStringValue ( " " ) // exclude whitespace
132+ )
117133 }
118134
119135 override predicate isShellInterpreted ( DataFlow:: Node arg ) {
120- // $({shell: true})`${sink } ${sink } .. ${sink }`
136+ // $({shell: true})`${cmd } ${arg0 } ... ${arg1 }`
121137 // ISSUE: $`cmd args` I can't reach the tag function argument easily
122- exists ( TemplateLiteral tmpL | templateLiteralChildAsSink ( this .asExpr ( ) ) = tmpL |
123- arg .asExpr ( ) = tmpL .getAChildExpr + ( ) and
124- isExecaShellEnableWithExpr ( this .asExpr ( ) .( CallExpr ) .getArgument ( 0 ) )
138+ exists ( TemplateLiteral tmpL | isFirstTaggedTemplateParameter ( this .asExpr ( ) , tmpL ) |
139+ arg .asExpr ( ) = tmpL .getAChildExpr ( ) and
140+ isExecaShellEnableWithExpr ( this .asExpr ( ) .( CallExpr ) .getArgument ( 0 ) ) and
141+ not arg .asExpr ( ) .mayHaveStringValue ( " " ) // exclude whitespace
125142 )
126143 }
127144
128145 override DataFlow:: Node getArgumentList ( ) {
129- // $`${Can Not Be sink} ${sink} .. ${sink}`
130- exists ( TemplateLiteral tmpL | templateLiteralChildAsSink ( this .asExpr ( ) ) = tmpL |
131- result .asExpr ( ) = tmpL .getAChildExpr + ( ) and
132- not result .asExpr ( ) = tmpL .getChildExpr ( 0 )
146+ // $`${cmd} ${arg0} ... ${argn}`
147+ exists ( TemplateLiteral tmpL | isFirstTaggedTemplateParameter ( this .asExpr ( ) , tmpL ) |
148+ result .asExpr ( ) = tmpL .getAChildExpr ( ) and
149+ not result .asExpr ( ) = tmpL .getChildExpr ( 0 ) and
150+ not result .asExpr ( ) .mayHaveStringValue ( " " ) // exclude whitespace
133151 )
134152 }
135153
136- override predicate isSync ( ) { name = "Sync" }
154+ override predicate isSync ( ) { isSync = true }
137155
138156 override DataFlow:: Node getOptionsArg ( ) {
139- result = this .asExpr ( ) .getAChildExpr * ( ) .flow ( ) and result .asExpr ( ) instanceof ObjectExpr
157+ result = this .asExpr ( ) .getAChildExpr ( ) .flow ( ) and result .asExpr ( ) instanceof ObjectExpr
140158 }
141159 }
142160
143161 /**
144162 * A call to `execa.execaCommandSync` or `execa.execaCommand`
145163 */
146164 private class ExecaCommandCall extends API:: CallNode {
147- string name ;
165+ boolean isSync ;
148166
149167 ExecaCommandCall ( ) {
150168 this = API:: moduleImport ( "execa" ) .getMember ( "execaCommandSync" ) .getACall ( ) and
151- name = "execaCommandSync"
169+ isSync = true
152170 or
153171 this = API:: moduleImport ( "execa" ) .getMember ( "execaCommand" ) .getACall ( ) and
154- name = "execaCommand"
172+ isSync = false
155173 }
156-
157- /** Gets the name of the exported function, such as `rm` in `shelljs.rm()`. */
158- string getName ( ) { result = name }
159- }
160-
161- /**
162- * The system command execution nodes for `execa.execaCommand` or `execa.execaCommandSync` functions
163- */
164- class ExecaCommandExec2 extends SystemCommandExecution , DataFlow:: CallNode {
165- ExecaCommandExec2 ( ) { this = API:: moduleImport ( "execa" ) .getMember ( "execaCommand" ) .getACall ( ) }
166-
167- override DataFlow:: Node getACommandArgument ( ) { result = this .getArgument ( 0 ) }
168-
169- override DataFlow:: Node getArgumentList ( ) { result = this .getArgument ( 0 ) }
170-
171- override predicate isShellInterpreted ( DataFlow:: Node arg ) { arg = this .getArgument ( 0 ) }
172-
173- override predicate isSync ( ) { none ( ) }
174-
175- override DataFlow:: Node getOptionsArg ( ) { result = this }
176174 }
177175
178176 /**
179177 * The system command execution nodes for `execa.execaCommand` or `execa.execaCommandSync` functions
180178 */
181179 class ExecaCommandExec extends SystemCommandExecution , ExecaCommandCall {
182- ExecaCommandExec ( ) { name = [ "execaCommand" , "execaCommandSync" ] }
180+ ExecaCommandExec ( ) { isSync = [ false , true ] }
183181
184182 override DataFlow:: Node getACommandArgument ( ) {
185183 result = this .( DataFlow:: CallNode ) .getArgument ( 0 )
186184 }
187185
188186 override DataFlow:: Node getArgumentList ( ) {
189- // execaCommand("echo " + sink);
190- // execaCommand(`echo ${sink}`);
191- result .asExpr ( ) = this .getParameter ( 0 ) .asSink ( ) .asExpr ( ) .getAChildExpr + ( ) and
187+ // execaCommand(`${cmd} ${arg}`);
188+ result .asExpr ( ) = this .getParameter ( 0 ) .asSink ( ) .asExpr ( ) .getAChildExpr ( ) and
192189 not result .asExpr ( ) = this .getArgument ( 0 ) .asExpr ( ) .getChildExpr ( 0 )
193190 }
194191
195192 override predicate isShellInterpreted ( DataFlow:: Node arg ) {
196- // execaCommandSync(sink1 + sink2 , {shell: true})
193+ // execaCommandSync(`${cmd} ${arg}` , {shell: true})
197194 arg .asExpr ( ) = this .getArgument ( 0 ) .asExpr ( ) .getAChildExpr + ( ) and
198195 isExecaShellEnable ( this .getParameter ( 1 ) )
199196 or
@@ -203,22 +200,25 @@ module Execa {
203200 not exists ( this .getArgument ( 0 ) .asExpr ( ) .getChildExpr ( 1 ) )
204201 }
205202
206- override predicate isSync ( ) { name = "execaCommandSync" }
203+ override predicate isSync ( ) { isSync = true }
207204
208205 override DataFlow:: Node getOptionsArg ( ) {
209206 result = this .getLastArgument ( ) and result .asExpr ( ) instanceof ObjectExpr
210207 }
211208 }
212209
213210 // Holds if left parameter is the left child of a template literal and returns the template literal
214- private TemplateLiteral templateLiteralChildAsSink ( Expr left ) {
211+ private predicate isFirstTaggedTemplateParameter ( Expr left , TemplateLiteral templateLiteral ) {
215212 exists ( TaggedTemplateExpr parent |
216- parent .getTemplate ( ) = result and
213+ templateLiteral = parent .getTemplate ( ) and
217214 left = parent .getChildExpr ( 0 )
218215 )
219216 }
220217
221- // Holds whether Execa has shell enabled options or not, get Parameter responsible for options
218+ /**
219+ * Holds whether Execa has shell enabled options or not, get Parameter responsible for options
220+ */
221+ pragma [ inline]
222222 private predicate isExecaShellEnable ( API:: Node n ) {
223223 n .getMember ( "shell" ) .asSink ( ) .asExpr ( ) .( BooleanLiteral ) .getValue ( ) = "true"
224224 }
0 commit comments