4
4
5
5
import javascript
6
6
import semmle.javascript.security.dataflow.RequestForgeryCustomizations
7
- import semmle.javascript.security.dataflow.UrlConcatenation
8
7
9
8
/**
10
9
* Provide model for [Execa](https://github.com/sindresorhus/execa) package
11
10
*/
12
11
module Execa {
13
12
/**
14
- * The Execa input file option
13
+ * The Execa input file read and output file write
15
14
*/
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 ;
18
18
19
- ExecaRead ( ) {
19
+ ExecaFileSystemAccess ( ) {
20
20
(
21
- execaNode = API:: moduleImport ( "execa" ) .getMember ( "$" ) .getParameter ( 0 )
21
+ execaArg = API:: moduleImport ( "execa" ) .getMember ( "$" ) .getParameter ( 0 ) and
22
+ isPipedToFile = false
22
23
or
23
- execaNode =
24
+ execaArg =
24
25
API:: moduleImport ( "execa" )
25
26
.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
27
37
) and
28
- this = execaNode .asSink ( )
38
+ this = execaArg .asSink ( )
29
39
}
30
40
31
- // data is the output of a command so IDK how it can be implemented
32
41
override DataFlow:: Node getADataNode ( ) { none ( ) }
33
42
34
43
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
36
47
}
37
48
}
38
49
39
50
/**
40
51
* A call to `execa.execa` or `execa.execaSync`
41
52
*/
42
53
class ExecaCall extends API:: CallNode {
43
- string name ;
54
+ boolean isSync ;
44
55
45
56
ExecaCall ( ) {
46
57
this = API:: moduleImport ( "execa" ) .getMember ( "execa" ) .getACall ( ) and
47
- name = "execa"
58
+ isSync = false
48
59
or
49
60
this = API:: moduleImport ( "execa" ) .getMember ( "execaSync" ) .getACall ( ) and
50
- name = "execaSync"
61
+ isSync = true
51
62
}
52
-
53
- /** Gets the name of the exported function, such as `rm` in `shelljs.rm()`. */
54
- string getName ( ) { result = name }
55
63
}
56
64
57
65
/**
58
66
* The system command execution nodes for `execa.execa` or `execa.execaSync` functions
59
67
*/
60
68
class ExecaExec extends SystemCommandExecution , ExecaCall {
61
- ExecaExec ( ) { name = [ "execa" , "execaSync" ] }
69
+ ExecaExec ( ) { isSync = [ false , true ] }
62
70
63
71
override DataFlow:: Node getACommandArgument ( ) { result = this .getArgument ( 0 ) }
64
72
@@ -73,7 +81,15 @@ module Execa {
73
81
isExecaShellEnable ( this .getParameter ( 1 ) )
74
82
}
75
83
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 }
77
93
78
94
override DataFlow:: Node getOptionsArg ( ) {
79
95
result = this .getLastArgument ( ) and result .asExpr ( ) instanceof ObjectExpr
@@ -84,116 +100,97 @@ module Execa {
84
100
* A call to `execa.$` or `execa.$.sync` tag functions
85
101
*/
86
102
private class ExecaScriptExpr extends DataFlow:: ExprNode {
87
- string name ;
103
+ boolean isSync ;
88
104
89
105
ExecaScriptExpr ( ) {
90
106
this .asExpr ( ) =
91
107
[
92
108
API:: moduleImport ( "execa" ) .getMember ( "$" ) ,
93
109
API:: moduleImport ( "execa" ) .getMember ( "$" ) .getReturn ( )
94
110
] .getAValueReachableFromSource ( ) .asExpr ( ) and
95
- name = "ASync"
111
+ isSync = false
96
112
or
97
113
this .asExpr ( ) =
98
114
[
99
115
API:: moduleImport ( "execa" ) .getMember ( "$" ) .getMember ( "sync" ) ,
100
116
API:: moduleImport ( "execa" ) .getMember ( "$" ) .getMember ( "sync" ) .getReturn ( )
101
117
] .getAValueReachableFromSource ( ) .asExpr ( ) and
102
- name = "Sync"
118
+ isSync = true
103
119
}
104
-
105
- /** Gets the name of the exported function, such as `rm` in `shelljs.rm()`. */
106
- string getName ( ) { result = name }
107
120
}
108
121
109
122
/**
110
123
* The system command execution nodes for `execa.$` or `execa.$.sync` tag functions
111
124
*/
112
125
class ExecaScriptEec extends SystemCommandExecution , ExecaScriptExpr {
113
- ExecaScriptEec ( ) { name = [ "Sync" , "ASync" ] }
126
+ ExecaScriptEec ( ) { isSync = [ false , true ] }
114
127
115
128
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
+ )
117
133
}
118
134
119
135
override predicate isShellInterpreted ( DataFlow:: Node arg ) {
120
- // $({shell: true})`${sink } ${sink } .. ${sink }`
136
+ // $({shell: true})`${cmd } ${arg0 } ... ${arg1 }`
121
137
// 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
125
142
)
126
143
}
127
144
128
145
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
133
151
)
134
152
}
135
153
136
- override predicate isSync ( ) { name = "Sync" }
154
+ override predicate isSync ( ) { isSync = true }
137
155
138
156
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
140
158
}
141
159
}
142
160
143
161
/**
144
162
* A call to `execa.execaCommandSync` or `execa.execaCommand`
145
163
*/
146
164
private class ExecaCommandCall extends API:: CallNode {
147
- string name ;
165
+ boolean isSync ;
148
166
149
167
ExecaCommandCall ( ) {
150
168
this = API:: moduleImport ( "execa" ) .getMember ( "execaCommandSync" ) .getACall ( ) and
151
- name = "execaCommandSync"
169
+ isSync = true
152
170
or
153
171
this = API:: moduleImport ( "execa" ) .getMember ( "execaCommand" ) .getACall ( ) and
154
- name = "execaCommand"
172
+ isSync = false
155
173
}
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 }
176
174
}
177
175
178
176
/**
179
177
* The system command execution nodes for `execa.execaCommand` or `execa.execaCommandSync` functions
180
178
*/
181
179
class ExecaCommandExec extends SystemCommandExecution , ExecaCommandCall {
182
- ExecaCommandExec ( ) { name = [ "execaCommand" , "execaCommandSync" ] }
180
+ ExecaCommandExec ( ) { isSync = [ false , true ] }
183
181
184
182
override DataFlow:: Node getACommandArgument ( ) {
185
183
result = this .( DataFlow:: CallNode ) .getArgument ( 0 )
186
184
}
187
185
188
186
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
192
189
not result .asExpr ( ) = this .getArgument ( 0 ) .asExpr ( ) .getChildExpr ( 0 )
193
190
}
194
191
195
192
override predicate isShellInterpreted ( DataFlow:: Node arg ) {
196
- // execaCommandSync(sink1 + sink2 , {shell: true})
193
+ // execaCommandSync(`${cmd} ${arg}` , {shell: true})
197
194
arg .asExpr ( ) = this .getArgument ( 0 ) .asExpr ( ) .getAChildExpr + ( ) and
198
195
isExecaShellEnable ( this .getParameter ( 1 ) )
199
196
or
@@ -203,22 +200,25 @@ module Execa {
203
200
not exists ( this .getArgument ( 0 ) .asExpr ( ) .getChildExpr ( 1 ) )
204
201
}
205
202
206
- override predicate isSync ( ) { name = "execaCommandSync" }
203
+ override predicate isSync ( ) { isSync = true }
207
204
208
205
override DataFlow:: Node getOptionsArg ( ) {
209
206
result = this .getLastArgument ( ) and result .asExpr ( ) instanceof ObjectExpr
210
207
}
211
208
}
212
209
213
210
// 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 ) {
215
212
exists ( TaggedTemplateExpr parent |
216
- parent .getTemplate ( ) = result and
213
+ templateLiteral = parent .getTemplate ( ) and
217
214
left = parent .getChildExpr ( 0 )
218
215
)
219
216
}
220
217
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]
222
222
private predicate isExecaShellEnable ( API:: Node n ) {
223
223
n .getMember ( "shell" ) .asSink ( ) .asExpr ( ) .( BooleanLiteral ) .getValue ( ) = "true"
224
224
}
0 commit comments