Skip to content

Commit 730580a

Browse files
authored
Merge pull request #19049 from Napalys/js/underscore-string
JS: Modeling of `underscore.string` package
2 parents d9c1589 + 9e78755 commit 730580a

File tree

5 files changed

+196
-0
lines changed

5 files changed

+196
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added support for the `underscore.string` package.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/javascript-all
4+
extensible: typeModel
5+
data:
6+
- ["'underscore.string'.Wrapper", "'underscore.string'", "ReturnValue"]
7+
- ["'underscore.string'.Wrapper", "'underscore.string'.Wrapper", "Member[slugify,capitalize,decapitalize,clean,cleanDiacritics,swapCase,escapeHTML,unescapeHTML,wrap,dedent,reverse,pred,succ,titleize,camelize,classify,underscored,dasherize,humanize,trim,ltrim,rtrim,truncate,sprintf,strRight,strRightBack,strLeft,strLeftBack,stripTags,unquote,strip,lstrip,rstrip,camelcase].ReturnValue"]
8+
- ["'underscore.string'.Wrapper", "'underscore.string'.Wrapper", "Member[insert,replaceAll,join,splice,prune,pad,lpad,rpad,repeat,surround,quote,q,rjust,ljust].ReturnValue"]
9+
- ["'underscore.string'.Wrapper", "'underscore.string'.Wrapper", "Member[toUpperCase,toLowerCase,replace,slice,substring,substr,concat].ReturnValue"]
10+
- ["'underscore.string'.Wrapper", "'underscore.string'.Wrapper", "Member[tap].ReturnValue"]
11+
12+
- addsTo:
13+
pack: codeql/javascript-all
14+
extensible: summaryModel
15+
data:
16+
- ["'underscore.string'", "Member[slugify,capitalize,decapitalize,clean,cleanDiacritics,swapCase,escapeHTML,unescapeHTML,wrap,dedent,reverse,pred,succ,titleize,camelize,classify,underscored,dasherize,humanize,trim,ltrim,rtrim,truncate,sprintf,strRight,strRightBack,strLeft,strLeftBack,stripTags,unquote,strip,lstrip,rstrip,camelcase]", "Argument[0]", "ReturnValue", "taint"]
17+
- ["'underscore.string'", "Member[chop,chars,words,lines]", "Argument[0]", "ReturnValue.ArrayElement", "taint"]
18+
- ["'underscore.string'", "Member[toSentence,toSentenceSerial]", "Argument[0].ArrayElement", "ReturnValue", "taint"]
19+
- ["'underscore.string'", "Member[insert,replaceAll,splice,prune,pad,lpad,rpad,repeat,rjust,ljust]", "Argument[0,2]", "ReturnValue", "taint"]
20+
- ["'underscore.string'", "Member[splice]", "Argument[0,3]", "ReturnValue", "taint"]
21+
- ["'underscore.string'", "Member[join]", "Argument[0..]", "ReturnValue", "taint"]
22+
- ["'underscore.string'", "Member[surround,quote,q]", "Argument[0,1]", "ReturnValue", "taint"]
23+
- ["'underscore.string'", "", "Argument[0]", "ReturnValue", "taint"]
24+
- ["'underscore.string'.Wrapper", "Member[slugify,capitalize,decapitalize,clean,cleanDiacritics,swapCase,escapeHTML,unescapeHTML,wrap,dedent,reverse,pred,succ,titleize,camelize,classify,underscored,dasherize,humanize,trim,ltrim,rtrim,truncate,sprintf,strRight,strRightBack,strLeft,strLeftBack,stripTags,unquote,value,strip,lstrip,rstrip,camelcase]", "Argument[this]", "ReturnValue", "taint"]
25+
- ["'underscore.string'.Wrapper", "Member[insert,replaceAll,join,splice,prune,pad,lpad,rpad,repeat,surround,quote,q,rjust,ljust]", "Argument[this]", "ReturnValue", "taint"]
26+
- ["'underscore.string'.Wrapper", "Member[insert,replaceAll,prune,pad,lpad,rpad,repeat,rjust,ljust]", "Argument[1]", "ReturnValue", "taint"]
27+
- ["'underscore.string'.Wrapper", "Member[surround,quote,q]", "Argument[0]", "ReturnValue", "taint"]
28+
- ["'underscore.string'.Wrapper", "Member[splice]", "Argument[2]", "ReturnValue", "taint"]
29+
- ["'underscore.string'.Wrapper", "Member[join,concat]", "Argument[0..]", "ReturnValue", "taint"]
30+
- ["'underscore.string'.Wrapper", "Member[toUpperCase,toLowerCase,replace,slice,substring,substr,split]", "Argument[this]", "ReturnValue", "taint"]
31+
- ["'underscore.string'.Wrapper", "Member[tap]", "Argument[this]", "ReturnValue", "taint"]
32+
- ["'underscore.string'.Wrapper", "Member[tap]", "Argument[0].ReturnValue", "ReturnValue", "taint"]
33+
- ["'underscore.string'.Wrapper", "Member[tap]", "Argument[this]", "Argument[0].Parameter[1]", "taint"]
34+
- ["'underscore.string'", "Member[map]", "Argument[0]", "Argument[1].Parameter[0]", "taint"]
35+
- ["'underscore.string'", "Member[map]", "Argument[1].ReturnValue", "ReturnValue", "taint"]

javascript/ql/lib/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,4 @@ import semmle.javascript.linters.ESLint
143143
import semmle.javascript.linters.JSLint
144144
import semmle.javascript.linters.Linting
145145
import semmle.javascript.security.dataflow.RemoteFlowSources
146+
import semmle.javascript.frameworks.UnderscoreDotString
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Provides classes for modeling data flow behavior of the Underscore.string library (https://www.npmjs.com/package/underscore.string).
3+
*/
4+
5+
private import javascript
6+
private import semmle.javascript.dataflow.internal.AdditionalFlowInternal
7+
8+
/**
9+
* Models data flow for the Underscore.string library.
10+
*/
11+
private class UnderscoreDotString extends AdditionalFlowInternal {
12+
/**
13+
* Some of the methods in `underscore.string` have the same name as methods from `Array.prototype`.
14+
* This prevents methods like `splice` from propagating into Argument[this].ArrayElement.
15+
*/
16+
override predicate clearsContent(DataFlow::Node node, DataFlow::ContentSet contents) {
17+
exists(DataFlow::CallNode call |
18+
call =
19+
ModelOutput::getATypeNode(["'underscore.string'.Wrapper", "'underscore.string'"])
20+
.getAMember()
21+
.getACall() and
22+
node = call.getReceiver().getPostUpdateNode() and
23+
contents = DataFlow::ContentSet::arrayElement()
24+
)
25+
}
26+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
var s = require("underscore.string");
2+
3+
function strToStr() {
4+
sink(s.slugify(source("s1"))); // $ hasTaintFlow=s1
5+
sink(s.capitalize(source("s2"))); // $ hasTaintFlow=s2
6+
sink(s.decapitalize(source("s3"))); // $ hasTaintFlow=s3
7+
sink(s.clean(source("s4"))); // $ hasTaintFlow=s4
8+
sink(s.cleanDiacritics(source("s5"))); // $ hasTaintFlow=s5
9+
sink(s.swapCase(source("s6"))); // $ hasTaintFlow=s6
10+
sink(s.escapeHTML(source("s7"))); // $ hasTaintFlow=s7
11+
sink(s.unescapeHTML(source("s8"))); // $ hasTaintFlow=s8
12+
sink(s.wrap(source("s9"), {})); // $ hasTaintFlow=s9
13+
sink(s.dedent(source("s10"), " ")); // $ hasTaintFlow=s10
14+
sink(s.reverse(source("s11"))); // $ hasTaintFlow=s11
15+
sink(s.pred(source("s12"))); // $ hasTaintFlow=s12
16+
sink(s.succ(source("s13"))); // $ hasTaintFlow=s13
17+
sink(s.titleize(source("s14"))); // $ hasTaintFlow=s14
18+
sink(s.camelize(source("s15"))); // $ hasTaintFlow=s15
19+
sink(s.classify(source("s16"))); // $ hasTaintFlow=s16
20+
sink(s.underscored(source("s17"))); // $ hasTaintFlow=s17
21+
sink(s.dasherize(source("s18"))); // $ hasTaintFlow=s18
22+
sink(s.humanize(source("s19"))); // $ hasTaintFlow=s19
23+
sink(s.trim(source("s20"),"charsToStrim")); // $ hasTaintFlow=s20
24+
sink(s.ltrim(source("s21"),"charsToStrim")); // $ hasTaintFlow=s21
25+
sink(s.rtrim(source("s22"),"charsToStrim")); // $ hasTaintFlow=s22
26+
sink(s.truncate(source("s23"), 10)); // $ hasTaintFlow=s23
27+
sink(s.sprintf(source("s24"), 1.17)); // $ hasTaintFlow=s24
28+
sink(s.strRight(source("s25"), "pattern")); // $ hasTaintFlow=s25
29+
sink(s.strRightBack(source("s26"), "pattern")); // $ hasTaintFlow=s26
30+
sink(s.strLeft(source("s27"), "pattern")); // $ hasTaintFlow=s27
31+
sink(s.strLeftBack(source("s28"), "pattern")); // $ hasTaintFlow=s28
32+
sink(s.stripTags(source("s29"))); // $ hasTaintFlow=s29
33+
sink(s.unquote(source("s30"), "quote")); // $ hasTaintFlow=s30
34+
sink(s.map(source("s31"), (x) => {return x;})); // $ hasTaintFlow=s31
35+
sink(s.strip(source("s32"),"charsToStrim")); // $ hasTaintFlow=s32
36+
sink(s.lstrip(source("s33"),"charsToStrim")); // $ hasTaintFlow=s33
37+
sink(s.rstrip(source("s34"),"charsToStrim")); // $ hasTaintFlow=s34
38+
sink(s.camelcase(source("s35"))); // $ hasTaintFlow=s35
39+
}
40+
41+
function strToArray() {
42+
sink(s.chop(source("s1"), 3)); // $ MISSING: hasTaintFlow=s1
43+
sink(s.chars(source("s2"))[0]); // $ hasTaintFlow=s2
44+
sink(s.words(source("s3"))[0]); // $ hasTaintFlow=s3
45+
sink(s.lines(source("s7"))[0]); // $ hasTaintFlow=s7
46+
sink(s.chop(source("s1"), 3).length);
47+
}
48+
49+
function arrayToStr() {
50+
sink(s.toSentence([source("s1")])); // $ hasTaintFlow=s1
51+
sink(s.toSentenceSerial([source("s2")])); // $ hasTaintFlow=s2
52+
}
53+
54+
function multiSource() {
55+
sink(s.insert("str", 4, source("s1"))); // $ hasTaintFlow=s1
56+
sink(s.insert(source("s2"), 4, "")); // $ hasTaintFlow=s2
57+
58+
sink(s.replaceAll("astr", "a", source("s3"))); // $ hasTaintFlow=s3
59+
sink(s.replaceAll(source("s4"), "a", "")); // $ hasTaintFlow=s4
60+
61+
sink(s.join(",", source("s5"), "str")); // $ hasTaintFlow=s5
62+
sink(s.join(",", "str", source("s6"))); // $ hasTaintFlow=s6
63+
64+
sink(s.splice(source("s7"), 1, 2, "str")); // $ hasTaintFlow=s7
65+
sink(s.splice("str", 1, 2, source("s8"))); // $ hasTaintFlow=s8
66+
67+
sink(s.prune(source("s9"), 1, "additional")); // $ hasTaintFlow=s9
68+
sink(s.prune("base", 1, source("s10"))); // $ hasTaintFlow=s10
69+
70+
sink(s.pad(source("s11"), 10, "charsToPad", "right")); // $ hasTaintFlow=s11
71+
sink(s.pad("base", 10, source("s12"), "right")); // $ hasTaintFlow=s12
72+
73+
sink(s.lpad(source("s13"), 10, "charsToPad")); // $ hasTaintFlow=s13
74+
sink(s.lpad("base", 10, source("s14"))); // $ hasTaintFlow=s14
75+
76+
sink(s.rpad(source("s15"), 10, "charsToPad")); // $ hasTaintFlow=s15
77+
sink(s.rpad("base", 10, source("s16"))); // $ hasTaintFlow=s16
78+
79+
sink(s.repeat(source("s17"), 3, "seperator")); // $ hasTaintFlow=s17
80+
sink(s.repeat("base", 3, source("s18"))); // $ hasTaintFlow=s18
81+
82+
sink(s.surround(source("s19"), "wrap")); // $ hasTaintFlow=s19
83+
sink(s.surround("base", source("s20"))); // $ hasTaintFlow=s20
84+
85+
sink(s.quote(source("s21"), "quote")); // $ hasTaintFlow=s21
86+
sink(s.quote("base", source("s22"))); // $ hasTaintFlow=s22
87+
88+
sink(s.q(source("s23"), "quote")); // $ hasTaintFlow=s23
89+
sink(s.q("base", source("s24"))); // $ hasTaintFlow=s24
90+
91+
sink(s.rjust(source("s25"), 10, "charsToPad")); // $ hasTaintFlow=s25
92+
sink(s.rjust("base", 10, source("s26"))); // $ hasTaintFlow=s26
93+
94+
sink(s.ljust(source("s27"), 10, "charsToPad")); // $ hasTaintFlow=s27
95+
sink(s.ljust("base", 10, source("s28"))); // $ hasTaintFlow=s28
96+
}
97+
98+
function chaining() {
99+
sink(s(source("s1"))
100+
.slugify().capitalize().decapitalize().clean().cleanDiacritics()
101+
.swapCase().escapeHTML().unescapeHTML().wrap().dedent()
102+
.reverse().pred().succ().titleize().camelize().classify()
103+
.underscored().dasherize().humanize().trim().ltrim().rtrim()
104+
.truncate().sprintf().strRight().strRightBack()
105+
.strLeft().strLeftBack().stripTags().unquote().value()); // $ hasTaintFlow=s1
106+
107+
sink(s(source("s2"))
108+
.insert(4, source("s3")).replaceAll("a", source("s4"))
109+
.join(",", source("s5")).splice(1, 2, source("s6"))
110+
.prune(1, source("s7")).pad(10, source("s8"), "right")
111+
.lpad(10, source("s9")).rpad(10, source("s10"))
112+
.repeat(3, source("s11")).surround(source("s12"))
113+
.quote(source("s13")).value()); // $ hasTaintFlow=s2 hasTaintFlow=s3 hasTaintFlow=s4 hasTaintFlow=s5 hasTaintFlow=s6 hasTaintFlow=s7 hasTaintFlow=s8 hasTaintFlow=s9 hasTaintFlow=s10 hasTaintFlow=s11 hasTaintFlow=s12 hasTaintFlow=s13
114+
115+
sink(s(source("s14")).toUpperCase().toLowerCase().replace().slice(1).substring(1).substr(1).concat(source("s15")).split()); // $ hasTaintFlow=s14 hasTaintFlow=s15
116+
117+
sink(s(source("s16"))
118+
.strip().lstrip().rstrip().camelcase()
119+
.q(source("s17")).ljust(10, source("s18"))
120+
.rjust(10, source("s19"))); // $ hasTaintFlow=s16 hasTaintFlow=s17 hasTaintFlow=s18 hasTaintFlow=s19
121+
122+
sink(s(source("s20")).tap(function(value) {
123+
return value + source("s21");
124+
}).value()); // $ hasTaintFlow=s20 hasTaintFlow=s21
125+
}
126+
127+
function mapTests(){
128+
sink(s.map(source("s1"), (x) => {return x + source("s2");})); // $ hasTaintFlow=s1 hasTaintFlow=s2
129+
s.map(source("s1"), (x) => { sink(x); return x;}); // $ hasTaintFlow=s1
130+
}

0 commit comments

Comments
 (0)