@@ -157,3 +157,46 @@ private predicate isCommonWordMatcher(RegExpTerm t) {
157157 .getValue ( ) = "w"
158158 )
159159}
160+
161+ /**
162+ * Holds if `replace` has a pattern argument containing a regular expression
163+ * `dangerous` which matches a dangerous string beginning with `prefix`, in an
164+ * attempt to avoid a vulnerability of kind `kind`.
165+ */
166+ predicate isResult (
167+ StringSubstitutionCall replace , EmptyReplaceRegExpTerm dangerous , string prefix , string kind
168+ ) {
169+ exists ( EmptyReplaceRegExpTerm regexp |
170+ replace = regexp .getCall ( ) and
171+ dangerous .getRootTerm ( ) = regexp and
172+ // skip leading optional elements
173+ not dangerous .isNullable ( ) and
174+ // only warn about the longest match
175+ prefix = max ( string m | matchesDangerousPrefix ( dangerous , m , kind ) | m order by m .length ( ) , m ) and
176+ // only warn once per kind
177+ not exists ( EmptyReplaceRegExpTerm other |
178+ other = dangerous .getAChild + ( ) or other = dangerous .getPredecessor + ( )
179+ |
180+ matchesDangerousPrefix ( other , _, kind ) and
181+ not other .isNullable ( )
182+ ) and
183+ // avoid anchored terms
184+ not exists ( RegExpAnchor a | regexp = a .getRootTerm ( ) ) and
185+ // Don't flag replace operations that are called repeatedly in a loop, as they can actually work correctly.
186+ not replace .flowsTo ( replace .getReceiver + ( ) )
187+ )
188+ }
189+
190+ /**
191+ * Holds if `replace` has a pattern argument containing a regular expression
192+ * `dangerous` which matches a dangerous string beginning with `prefix`. `msg`
193+ * is the alert we report.
194+ */
195+ query predicate problems (
196+ StringSubstitutionCall replace , string msg , EmptyReplaceRegExpTerm dangerous , string prefix
197+ ) {
198+ exists ( string kind |
199+ isResult ( replace , dangerous , prefix , kind ) and
200+ msg = "This string may still contain $@, which may cause a " + kind + " vulnerability."
201+ )
202+ }
0 commit comments