Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e8b233f
Added test cases `underscore.string` string to string.
Napalys Mar 17, 2025
9bca863
Added modeling of `underscore.string` string to string functions.
Napalys Mar 17, 2025
c256b9c
Added `underscore.string` test cases for str to array.
Napalys Mar 17, 2025
30623cd
Added modeling of `underscore.string` for str to array.
Napalys Mar 17, 2025
cd40b6f
Added test cases `underscore.string` array to string.
Napalys Mar 17, 2025
6b105b2
Added modeling `underscore.string` array to string functions.
Napalys Mar 17, 2025
77e1e17
Added test cases `underscore.string` with multiple sources.
Napalys Mar 17, 2025
b59b9c8
Added modeling `underscore.string` of function which contain multiple…
Napalys Mar 17, 2025
25c6fb5
Added chaining tests for `underscore.string` package.
Napalys Mar 17, 2025
ca9ae8a
Added chaining modeling for `underscore.string` package.
Napalys Mar 17, 2025
cccd863
Added test for extra chaining function for `underscore.string`.
Napalys Mar 17, 2025
3a83c8d
Added modeling for extra chaining function from `underscore.string`.
Napalys Mar 17, 2025
dcc1e88
Added test cases for aliases.
Napalys Mar 17, 2025
fc6b779
Added modeling for aliases.
Napalys Mar 17, 2025
eb18c3c
Added test case for `tap`.
Napalys Mar 17, 2025
d8e6d76
Added modeling for `tap` function.
Napalys Mar 17, 2025
2c7562d
Removed `value` from modeling its return value as Wrapper class, sinc…
Napalys Mar 17, 2025
8b431dc
Added change note.
Napalys Mar 17, 2025
922a07d
Added `underscore.string` `clearsContent`.
Napalys Mar 18, 2025
752f02f
Fixed `map` modeling and added test cases.
Napalys Mar 20, 2025
f4ca2dc
Restricted taint to array elements.
Napalys Mar 20, 2025
221cc19
Merge branch 'main' into js/underscore-string
Napalys Mar 20, 2025
ca53e97
Adressed comments.
Napalys Mar 20, 2025
9e78755
Fixed typo in the test cases.
Napalys Mar 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added support for the `underscore.string` package.
35 changes: 35 additions & 0 deletions javascript/ql/lib/ext/underscore.string.model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: typeModel
data:
- ["'underscore.string'.Wrapper", "'underscore.string'", "ReturnValue"]
- ["'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"]
- ["'underscore.string'.Wrapper", "'underscore.string'.Wrapper", "Member[insert,replaceAll,join,splice,prune,pad,lpad,rpad,repeat,surround,quote,q,rjust,ljust].ReturnValue"]
- ["'underscore.string'.Wrapper", "'underscore.string'.Wrapper", "Member[toUpperCase,toLowerCase,replace,slice,substring,substr,concat].ReturnValue"]
- ["'underscore.string'.Wrapper", "'underscore.string'.Wrapper", "Member[tap].ReturnValue"]

- addsTo:
pack: codeql/javascript-all
extensible: summaryModel
data:
- ["'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"]
- ["'underscore.string'", "Member[chop,chars,words,lines]", "Argument[0]", "ReturnValue.ArrayElement", "taint"]
- ["'underscore.string'", "Member[toSentence,toSentenceSerial]", "Argument[0].ArrayElement", "ReturnValue", "taint"]
- ["'underscore.string'", "Member[insert,replaceAll,splice,prune,pad,lpad,rpad,repeat,rjust,ljust]", "Argument[0,2]", "ReturnValue", "taint"]
- ["'underscore.string'", "Member[splice]", "Argument[0,3]", "ReturnValue", "taint"]
- ["'underscore.string'", "Member[join]", "Argument[0..]", "ReturnValue", "taint"]
- ["'underscore.string'", "Member[surround,quote,q]", "Argument[0,1]", "ReturnValue", "taint"]
- ["'underscore.string'", "", "Argument[0]", "ReturnValue", "taint"]
- ["'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"]
- ["'underscore.string'.Wrapper", "Member[insert,replaceAll,join,splice,prune,pad,lpad,rpad,repeat,surround,quote,q,rjust,ljust]", "Argument[this]", "ReturnValue", "taint"]
- ["'underscore.string'.Wrapper", "Member[insert,replaceAll,prune,pad,lpad,rpad,repeat,rjust,ljust]", "Argument[1]", "ReturnValue", "taint"]
- ["'underscore.string'.Wrapper", "Member[surround,quote,q]", "Argument[0]", "ReturnValue", "taint"]
- ["'underscore.string'.Wrapper", "Member[splice]", "Argument[2]", "ReturnValue", "taint"]
- ["'underscore.string'.Wrapper", "Member[join,concat]", "Argument[0..]", "ReturnValue", "taint"]
- ["'underscore.string'.Wrapper", "Member[toUpperCase,toLowerCase,replace,slice,substring,substr,split]", "Argument[this]", "ReturnValue", "taint"]
- ["'underscore.string'.Wrapper", "Member[tap]", "Argument[this]", "ReturnValue", "taint"]
- ["'underscore.string'.Wrapper", "Member[tap]", "Argument[0].ReturnValue", "ReturnValue", "taint"]
- ["'underscore.string'.Wrapper", "Member[tap]", "Argument[this]", "Argument[0].Parameter[1]", "taint"]
- ["'underscore.string'", "Member[map]", "Argument[0]", "Argument[1].Parameter[0]", "taint"]
- ["'underscore.string'", "Member[map]", "Argument[1].ReturnValue", "ReturnValue", "taint"]
1 change: 1 addition & 0 deletions javascript/ql/lib/javascript.qll
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,4 @@ import semmle.javascript.linters.ESLint
import semmle.javascript.linters.JSLint
import semmle.javascript.linters.Linting
import semmle.javascript.security.dataflow.RemoteFlowSources
import semmle.javascript.frameworks.UnderscoreDotString
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Provides classes for modeling data flow behavior of the Underscore.string library (https://www.npmjs.com/package/underscore.string).
*/

private import javascript
private import semmle.javascript.dataflow.internal.AdditionalFlowInternal

/**
* Models data flow for the Underscore.string library.
*/
private class UnderscoreDotString extends AdditionalFlowInternal {
/**
* Some of the methods in `underscore.string` have the same name as methods from `Array.prototype`.
* This prevents methods like `splice` from propagating into Argument[this].ArrayElement.
*/
override predicate clearsContent(DataFlow::Node node, DataFlow::ContentSet contents) {
exists(DataFlow::CallNode call |
call =
ModelOutput::getATypeNode(["'underscore.string'.Wrapper", "'underscore.string'"])
.getAMember()
.getACall() and
node = call.getReceiver().getPostUpdateNode() and
contents = DataFlow::ContentSet::arrayElement()
)
}
}
130 changes: 130 additions & 0 deletions javascript/ql/test/library-tests/TripleDot/underscore.string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
var s = require("underscore.string");

function strToStr() {
sink(s.slugify(source("s1"))); // $ hasTaintFlow=s1
sink(s.capitalize(source("s2"))); // $ hasTaintFlow=s2
sink(s.decapitalize(source("s3"))); // $ hasTaintFlow=s3
sink(s.clean(source("s4"))); // $ hasTaintFlow=s4
sink(s.cleanDiacritics(source("s5"))); // $ hasTaintFlow=s5
sink(s.swapCase(source("s6"))); // $ hasTaintFlow=s6
sink(s.escapeHTML(source("s7"))); // $ hasTaintFlow=s7
sink(s.unescapeHTML(source("s8"))); // $ hasTaintFlow=s8
sink(s.wrap(source("s9"), {})); // $ hasTaintFlow=s9
sink(s.dedent(source("s10"), " ")); // $ hasTaintFlow=s10
sink(s.reverse(source("s11"))); // $ hasTaintFlow=s11
sink(s.pred(source("s12"))); // $ hasTaintFlow=s12
sink(s.succ(source("s13"))); // $ hasTaintFlow=s13
sink(s.titleize(source("s14"))); // $ hasTaintFlow=s14
sink(s.camelize(source("s15"))); // $ hasTaintFlow=s15
sink(s.classify(source("s16"))); // $ hasTaintFlow=s16
sink(s.underscored(source("s17"))); // $ hasTaintFlow=s17
sink(s.dasherize(source("s18"))); // $ hasTaintFlow=s18
sink(s.humanize(source("s19"))); // $ hasTaintFlow=s19
sink(s.trim(source("s20"),"charsToStrim")); // $ hasTaintFlow=s20
sink(s.ltrim(source("s21"),"charsToStrim")); // $ hasTaintFlow=s21
sink(s.rtrim(source("s22"),"charsToStrim")); // $ hasTaintFlow=s22
sink(s.truncate(source("s23"), 10)); // $ hasTaintFlow=s23
sink(s.sprintf(source("s24"), 1.17)); // $ hasTaintFlow=s24
sink(s.strRight(source("s25"), "pattern")); // $ hasTaintFlow=s25
sink(s.strRightBack(source("s26"), "pattern")); // $ hasTaintFlow=s26
sink(s.strLeft(source("s27"), "pattern")); // $ hasTaintFlow=s27
sink(s.strLeftBack(source("s28"), "pattern")); // $ hasTaintFlow=s28
sink(s.stripTags(source("s29"))); // $ hasTaintFlow=s29
sink(s.unquote(source("s30"), "quote")); // $ hasTaintFlow=s30
sink(s.map(source("s31"), (x) => {return x;})); // $ hasTaintFlow=s31
sink(s.strip(source("s32"),"charsToStrim")); // $ hasTaintFlow=s32
sink(s.lstrip(source("s33"),"charsToStrim")); // $ hasTaintFlow=s33
sink(s.rstrip(source("s34"),"charsToStrim")); // $ hasTaintFlow=s34
sink(s.camelcase(source("s35"))); // $ hasTaintFlow=s35
}

function strToArray() {
sink(s.chop(source("s1"), 3)); // $ MISSING: hasTaintFlow=s1
sink(s.chars(source("s2"))[0]); // $ hasTaintFlow=s2
sink(s.words(source("s3"))[0]); // $ hasTaintFlow=s3
sink(s.lines(source("s7"))[0]); // $ hasTaintFlow=s7
sink(s.chop(source("s1"), 3).length);
}

function arrayToStr() {
sink(s.toSentence([source("s1")])); // $ hasTaintFlow=s1
sink(s.toSentenceSerial([source("s2")])); // $ hasTaintFlow=s2
}

function multiSource() {
sink(s.insert("str", 4, source("s1"))); // $ hasTaintFlow=s1
sink(s.insert(source("s2"), 4, "")); // $ hasTaintFlow=s2

sink(s.replaceAll("astr", "a", source("s3"))); // $ hasTaintFlow=s3
sink(s.replaceAll(source("s4"), "a", "")); // $ hasTaintFlow=s4

sink(s.join(",", source("s5"), "str")); // $ hasTaintFlow=s5
sink(s.join(",", "str", source("s6"))); // $ hasTaintFlow=s6

sink(s.splice(source("s7"), 1, 2, "str")); // $ hasTaintFlow=s7
sink(s.splice("str", 1, 2, source("s8"))); // $ hasTaintFlow=s8

sink(s.prune(source("s9"), 1, "additional")); // $ hasTaintFlow=s9
sink(s.prune("base", 1, source("s10"))); // $ hasTaintFlow=s10

sink(s.pad(source("s11"), 10, "charsToPad", "right")); // $ hasTaintFlow=s11
sink(s.pad("base", 10, source("s12"), "right")); // $ hasTaintFlow=s12

sink(s.lpad(source("s13"), 10, "charsToPad")); // $ hasTaintFlow=s13
sink(s.lpad("base", 10, source("s14"))); // $ hasTaintFlow=s14

sink(s.rpad(source("s15"), 10, "charsToPad")); // $ hasTaintFlow=s15
sink(s.rpad("base", 10, source("s16"))); // $ hasTaintFlow=s16

sink(s.repeat(source("s17"), 3, "seperator")); // $ hasTaintFlow=s17
sink(s.repeat("base", 3, source("s18"))); // $ hasTaintFlow=s18

sink(s.surround(source("s19"), "wrap")); // $ hasTaintFlow=s19
sink(s.surround("base", source("s20"))); // $ hasTaintFlow=s20

sink(s.quote(source("s21"), "quote")); // $ hasTaintFlow=s21
sink(s.quote("base", source("s22"))); // $ hasTaintFlow=s22

sink(s.q(source("s23"), "quote")); // $ hasTaintFlow=s23
sink(s.q("base", source("s24"))); // $ hasTaintFlow=s24

sink(s.rjust(source("s25"), 10, "charsToPad")); // $ hasTaintFlow=s25
sink(s.rjust("base", 10, source("s26"))); // $ hasTaintFlow=s26

sink(s.ljust(source("s27"), 10, "charsToPad")); // $ hasTaintFlow=s27
sink(s.ljust("base", 10, source("s28"))); // $ hasTaintFlow=s28
}

function chaining() {
sink(s(source("s1"))
.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()); // $ hasTaintFlow=s1

sink(s(source("s2"))
.insert(4, source("s3")).replaceAll("a", source("s4"))
.join(",", source("s5")).splice(1, 2, source("s6"))
.prune(1, source("s7")).pad(10, source("s8"), "right")
.lpad(10, source("s9")).rpad(10, source("s10"))
.repeat(3, source("s11")).surround(source("s12"))
.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

sink(s(source("s14")).toUpperCase().toLowerCase().replace().slice(1).substring(1).substr(1).concat(source("s15")).split()); // $ hasTaintFlow=s14 hasTaintFlow=s15

sink(s(source("s16"))
.strip().lstrip().rstrip().camelcase()
.q(source("s17")).ljust(10, source("s18"))
.rjust(10, source("s19"))); // $ hasTaintFlow=s16 hasTaintFlow=s17 hasTaintFlow=s18 hasTaintFlow=s19

sink(s(source("s20")).tap(function(value) {
return value + source("s21");
}).value()); // $ hasTaintFlow=s20 hasTaintFlow=s21
}

function mapTests(){
sink(s.map(source("s1"), (x) => {return x + source("s2");})); // $ hasTaintFlow=s1 hasTaintFlow=s2
s.map(source("s1"), (x) => { sink(x); return x;}); // $ hasTaintFlow=s1
}