Merge remote-tracking branch 'origin/main' into smowton/admin/merge-rc317-into-main

This commit is contained in:
Chris Smowton
2025-03-19 16:01:29 +00:00
2476 changed files with 45571 additions and 29067 deletions

View File

@@ -0,0 +1,4 @@
---
category: majorAnalysis
---
* Added support for TypeScript 5.8.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added support for the `react-relay` library.

View File

@@ -0,0 +1,7 @@
---
category: feature
---
* Extraction now supports regular expressions with the `v` flag, using the new operators:
- Intersection `&&`
- Subtraction `--`
- `\q` quoted string

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Improved the modeling of the `markdown-table` package to ensure it handles nested arrays properly.

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* Added support for the `@tanstack/angular-query-experimental` package.
* Improved support for the `@angular/common/http` package, detecting outgoing HTTP requests in more cases.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added support for the `@tanstack/vue-query` package.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added taint-steps for `unescape()`.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added additional flow step for `unescape()` and `escape()`.

View File

@@ -0,0 +1,6 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: summaryModel
data:
- ["markdown-table", "", "Argument[0].ArrayElement.ArrayElement", "ReturnValue", "taint"]

View File

@@ -0,0 +1,15 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: sourceModel
data:
- ["react-relay", "Member[useFragment].ReturnValue", "response"]
- ["react-relay", "Member[useLazyLoadQuery].ReturnValue", "response"]
- ["react-relay", "Member[usePreloadedQuery].ReturnValue", "response"]
- ["react-relay", "Member[useClientQuery].ReturnValue", "response"]
- ["react-relay", "Member[useRefetchableFragment].ReturnValue.Member[0]", "response"]
- ["react-relay", "Member[usePaginationFragment].ReturnValue.Member[data]", "response"]
- ["react-relay", "Member[useMutation].ReturnValue.Member[0].Argument[0].Member[onCompleted].Parameter[0]", "response"]
- ["react-relay", "Member[useSubscription].Argument[0].Member[onNext].Parameter[0]", "response"]
- ["react-relay", "Member[fetchQuery].ReturnValue.Member[subscribe].Argument[0].Member[next].Parameter[0]", "response"]
- ["relay-runtime", "Member[readFragment].ReturnValue", "response"]

View File

@@ -0,0 +1,11 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: summaryModel
data:
- ["@tanstack/angular-query-experimental", "Member[injectQuery]", "Argument[0].ReturnValue.Member[queryFn].ReturnValue", "ReturnValue.Member[data].Awaited", "value"]
- ["@tanstack/angular-query", "Member[injectQuery]", "Argument[0].ReturnValue.Member[queryFn].ReturnValue", "ReturnValue.Member[data].Awaited", "value"]
- ["@tanstack/vue-query", "Member[useQuery]", "Argument[0].Member[queryFn].ReturnValue.Awaited", "ReturnValue.Member[data]", "value"]
- ["@tanstack/vue-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.ArrayElement.Member[data]", "value"]
- ["@tanstack/react-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.ArrayElement.Member[data]", "value"]
- ["@tanstack/react-query", "Member[useQuery]", "Argument[0].Member[queryFn].ReturnValue.Awaited", "ReturnValue.Member[data]", "value"]

View File

@@ -139,7 +139,6 @@ import semmle.javascript.frameworks.Webix
import semmle.javascript.frameworks.WebSocket
import semmle.javascript.frameworks.XmlParsers
import semmle.javascript.frameworks.xUnit
import semmle.javascript.frameworks.Tanstack
import semmle.javascript.linters.ESLint
import semmle.javascript.linters.JSLint
import semmle.javascript.linters.Linting

View File

@@ -8,6 +8,10 @@
import javascript
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
private import semmle.javascript.dataflow.internal.PreCallGraphStep
private import semmle.javascript.dataflow.internal.StepSummary
private import semmle.javascript.dataflow.internal.sharedlib.SummaryTypeTracker as SummaryTypeTracker
private import semmle.javascript.dataflow.internal.Contents::Private as ContentPrivate
private import semmle.javascript.DynamicPropertyAccess
private import internal.CachedStages
/**
@@ -220,15 +224,53 @@ module API {
}
/**
* Gets a node representing a member of this API component where the name of the member is
* not known statically.
* DEPRECATED. Use either `getArrayElement()` or `getAMember()` instead.
*/
deprecated Node getUnknownMember() { result = this.getArrayElement() }
/**
* Gets an array element of unknown index.
*/
cached
Node getUnknownMember() {
Node getUnknownArrayElement() {
Stages::ApiStage::ref() and
result = this.getASuccessor(Label::unknownMember())
result = this.getASuccessor(Label::content(ContentPrivate::MkArrayElementUnknown()))
}
cached
private Node getContentRaw(DataFlow::Content content) {
Stages::ApiStage::ref() and
result = this.getASuccessor(Label::content(content))
}
/**
* Gets a representative for the `content` of this value.
*
* When possible, it is preferrable to use one of the specialized variants of this predicate, such as `getMember`.
*/
pragma[inline]
Node getContent(DataFlow::Content content) {
result = this.getContentRaw(content)
or
result = this.getMember(content.asPropertyName())
}
/**
* Gets a representative for the `contents` of this value.
*/
bindingset[contents]
pragma[inline_late]
private Node getContents(DataFlow::ContentSet contents) {
// We always use getAStoreContent when generating content edges, and we always use getAReadContent when querying the graph.
result = this.getContent(contents.getAReadContent())
}
/**
* Gets a node representing an arbitrary array element in the array represented by this node.
*/
cached
Node getArrayElement() { result = this.getContents(DataFlow::ContentSet::arrayElement()) }
/**
* Gets a node representing a member of this API component where the name of the member may
* or may not be known statically.
@@ -238,7 +280,7 @@ module API {
Stages::ApiStage::ref() and
result = this.getMember(_)
or
result = this.getUnknownMember()
result = this.getUnknownArrayElement()
}
/**
@@ -790,6 +832,11 @@ module API {
not DataFlow::PseudoProperties::isPseudoProperty(prop)
)
or
exists(DataFlow::ContentSet contents |
SummaryTypeTracker::basicStoreStep(rhs, pred.getALocalUse(), contents) and
lbl = Label::content(contents.getAStoreContent())
)
or
exists(DataFlow::FunctionNode fn |
fn = pred and
lbl = Label::return()
@@ -982,6 +1029,11 @@ module API {
// avoid generating member edges like "$arrayElement$"
not DataFlow::PseudoProperties::isPseudoProperty(prop)
)
or
exists(DataFlow::ContentSet contents |
SummaryTypeTracker::basicLoadStep(pred.getALocalUse(), ref, contents) and
lbl = Label::content(contents.getAStoreContent())
)
)
or
exists(DataFlow::Node def, DataFlow::FunctionNode fn |
@@ -1199,8 +1251,6 @@ module API {
t = useStep(nd, promisified, boundArgs, prop, result)
}
private import semmle.javascript.dataflow.internal.StepSummary
/**
* Holds if `nd`, which is a use of an API-graph node, flows in zero or more potentially
* inter-procedural steps to some intermediate node, and then from that intermediate node to
@@ -1458,8 +1508,21 @@ module API {
bindingset[result]
LabelMember member(string m) { result.getProperty() = m }
/** Gets the `member` edge label for the unknown member. */
LabelUnknownMember unknownMember() { any() }
/** Gets the `content` edge label for content `c`. */
LabelContent content(ContentPrivate::Content c) { result.getContent() = c }
/**
* Gets the edge label for an unknown member.
*
* Currently this is represented the same way as an unknown array element, but this may
* change in the future.
*/
ApiLabel unknownMember() { result = arrayElement() }
/**
* Gets the edge label for an unknown array element.
*/
LabelContent arrayElement() { result.getContent().isUnknownArrayElement() }
/**
* Gets a property name referred to by the given dynamic property access,
@@ -1482,6 +1545,11 @@ module API {
result = unique(string s | s = getAnIndirectPropName(ref))
}
pragma[nomagic]
private predicate isEnumeratedPropName(DataFlow::Node node) {
node.getAPredecessor*() instanceof EnumeratedPropName
}
/** Gets the `member` edge label for the given property reference. */
ApiLabel memberFromRef(DataFlow::PropRef pr) {
exists(string pn | pn = pr.getPropertyName() or pn = getIndirectPropName(pr) |
@@ -1493,7 +1561,9 @@ module API {
or
not exists(pr.getPropertyName()) and
not exists(getIndirectPropName(pr)) and
result = unknownMember()
// Avoid assignments in an extend-like pattern
not isEnumeratedPropName(pr.getPropertyNameExpr().flow()) and
result = arrayElement()
}
/** Gets the `instance` edge label. */
@@ -1516,10 +1586,10 @@ module API {
LabelForwardingFunction forwardingFunction() { any() }
/** Gets the `promised` edge label connecting a promise to its contained value. */
LabelPromised promised() { any() }
LabelContent promised() { result.getContent() = ContentPrivate::MkPromiseValue() }
/** Gets the `promisedError` edge label connecting a promise to its rejected value. */
LabelPromisedError promisedError() { any() }
LabelContent promisedError() { result.getContent() = ContentPrivate::MkPromiseError() }
/** Gets the label for an edge leading from a value `D` to any class that has `D` as a decorator. */
LabelDecoratedClass decoratedClass() { any() }
@@ -1542,18 +1612,12 @@ module API {
exists(Impl::MkModuleImport(mod))
} or
MkLabelInstance() or
MkLabelMember(string prop) {
exports(_, prop, _) or
exists(any(DataFlow::ClassNode c).getInstanceMethod(prop)) or
prop = "exports" or
prop = any(CanonicalName c).getName() or
prop = any(DataFlow::PropRef p).getPropertyName() or
exists(Impl::MkTypeUse(_, prop)) or
exists(any(Module m).getAnExportedValue(prop)) or
PreCallGraphStep::loadStep(_, _, prop) or
PreCallGraphStep::storeStep(_, _, prop)
MkLabelContent(DataFlow::Content content) or
MkLabelMember(string name) {
name instanceof PropertyName
or
exists(Impl::MkTypeUse(_, name))
} or
MkLabelUnknownMember() or
MkLabelParameter(int i) {
i =
[0 .. max(int args |
@@ -1564,8 +1628,6 @@ module API {
} or
MkLabelReceiver() or
MkLabelReturn() or
MkLabelPromised() or
MkLabelPromisedError() or
MkLabelDecoratedClass() or
MkLabelDecoratedMember() or
MkLabelDecoratedParameter() or
@@ -1585,13 +1647,13 @@ module API {
}
/** A label that gets a promised value. */
class LabelPromised extends ApiLabel, MkLabelPromised {
override string toString() { result = "getPromised()" }
deprecated class LabelPromised extends ApiLabel {
LabelPromised() { this = MkLabelContent(ContentPrivate::MkPromiseValue()) }
}
/** A label that gets a rejected promise. */
class LabelPromisedError extends ApiLabel, MkLabelPromisedError {
override string toString() { result = "getPromisedError()" }
deprecated class LabelPromisedError extends ApiLabel {
LabelPromisedError() { this = MkLabelContent(ContentPrivate::MkPromiseError()) }
}
/** A label that gets the return value of a function. */
@@ -1617,9 +1679,39 @@ module API {
override string toString() { result = "getInstance()" }
}
/** A label for a content. */
class LabelContent extends ApiLabel, MkLabelContent {
private DataFlow::Content content;
LabelContent() {
this = MkLabelContent(content) and
// Property names are represented by LabelMember to ensure additional property
// names from PreCallGraph step are included, as well as those from MkTypeUse.
not content instanceof ContentPrivate::MkPropertyContent
}
/** Gets the content associated with this label. */
DataFlow::Content getContent() { result = content }
private string specialisedToString() {
content instanceof ContentPrivate::MkPromiseValue and result = "getPromised()"
or
content instanceof ContentPrivate::MkPromiseError and result = "getPromisedError()"
or
content instanceof ContentPrivate::MkArrayElementUnknown and result = "getArrayElement()"
}
override string toString() {
result = this.specialisedToString()
or
not exists(this.specialisedToString()) and
result = "getContent(" + content + ")"
}
}
/** A label for the member named `prop`. */
class LabelMember extends ApiLabel, MkLabelMember {
string prop;
private string prop;
LabelMember() { this = MkLabelMember(prop) }
@@ -1630,10 +1722,8 @@ module API {
}
/** A label for a member with an unknown name. */
class LabelUnknownMember extends ApiLabel, MkLabelUnknownMember {
LabelUnknownMember() { this = MkLabelUnknownMember() }
override string toString() { result = "getUnknownMember()" }
deprecated class LabelUnknownMember extends LabelContent {
LabelUnknownMember() { this.getContent().isUnknownArrayElement() }
}
/** A label for parameter `i`. */

View File

@@ -140,22 +140,17 @@ module MembershipCandidate {
EnumerationRegExp() {
this.isRootTerm() and
RegExp::isFullyAnchoredTerm(this) and
exists(RegExpTerm child | this.getAChild*() = child |
child instanceof RegExpSequence or
child instanceof RegExpCaret or
child instanceof RegExpDollar or
child instanceof RegExpConstant or
child instanceof RegExpAlt or
child instanceof RegExpGroup
) and
// exclude "length matches" that match every string
not this.getAChild*() instanceof RegExpDot
not exists(RegExpTerm child | child.getRootTerm() = this |
child instanceof RegExpDot or
child instanceof RegExpCharacterClass or
child instanceof RegExpUnicodePropertyEscape
)
}
/**
* Gets a string matched by this regular expression.
*/
string getAMember() { result = this.getAChild*().getAMatchedString() }
string getAMember() { result = any(RegExpTerm t | t.getRootTerm() = this).getAMatchedString() }
}
/**

View File

@@ -4,6 +4,7 @@
import javascript
private import dataflow.internal.StepSummary
private import semmle.javascript.dataflow.internal.FlowSteps
/**
* A call to the `Promise` constructor, such as `new Promise((resolve, reject) => { ... })`.
@@ -397,6 +398,17 @@ module PromiseFlow {
value = call.getCallback(0).getExceptionalReturn() and
obj = call
)
or
exists(DataFlow::FunctionNode f | f.getFunction().isAsync() |
// ordinary return
prop = valueProp() and
value = f.getAReturn() and
obj = f.getReturnNode()
or
// exceptional return
prop = errorProp() and
localExceptionStepWithAsyncFlag(value, obj, true)
)
}
/**
@@ -525,30 +537,6 @@ private class PromiseTaintStep extends TaintTracking::LegacyTaintStep {
* Defines flow steps for return on async functions.
*/
private module AsyncReturnSteps {
private predicate valueProp = Promises::valueProp/0;
private predicate errorProp = Promises::errorProp/0;
private import semmle.javascript.dataflow.internal.FlowSteps
/**
* A data-flow step for ordinary and exceptional returns from async functions.
*/
private class AsyncReturn extends LegacyPreCallGraphStep {
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
exists(DataFlow::FunctionNode f | f.getFunction().isAsync() |
// ordinary return
prop = valueProp() and
pred = f.getAReturn() and
succ = f.getReturnNode()
or
// exceptional return
prop = errorProp() and
localExceptionStepWithAsyncFlag(pred, succ, true)
)
}
}
/**
* A data-flow step for ordinary return from an async function in a taint configuration.
*/

View File

@@ -301,6 +301,51 @@ class RegExpAlt extends RegExpTerm, @regexp_alt {
override string getAPrimaryQlClass() { result = "RegExpAlt" }
}
/**
* An intersection term, that is, a term of the form `[[a]&&[ab]]`.
*
* Example:
*
* ```
* /[[abc]&&[bcd]]/v - which matches 'b' and 'c' only.
* ```
*/
class RegExpIntersection extends RegExpTerm, @regexp_intersection {
/** Gets an intersected term of this term. */
RegExpTerm getAnElement() { result = this.getAChild() }
/** Gets the number of intersected terms of this term. */
int getNumIntersectedTerm() { result = this.getNumChild() }
override predicate isNullable() { this.getAnElement().isNullable() }
override string getAPrimaryQlClass() { result = "RegExpIntersection" }
}
/**
* A subtraction term, that is, a term of the form `[[a]--[ab]]`.
*
* Example:
*
* ```
* /[[abc]--[bc]]/v - which matches 'a' only.
* ```
*/
class RegExpSubtraction extends RegExpTerm, @regexp_subtraction {
/** Gets the minuend (left operand) of this subtraction. */
RegExpTerm getFirstTerm() { result = this.getChild(0) }
/** Gets the number of subtractions terms of this term. */
int getNumSubtractedTerm() { result = this.getNumChild() - 1 }
/** Gets a subtrahend (right operand) of this subtraction. */
RegExpTerm getASubtractedTerm() { exists(int i | i > 0 and result = this.getChild(i)) }
override predicate isNullable() { none() }
override string getAPrimaryQlClass() { result = "RegExpSubtraction" }
}
/**
* A sequence term.
*
@@ -1142,6 +1187,28 @@ private class StringConcatRegExpPatternSource extends RegExpPatternSource {
override RegExpTerm getRegExpTerm() { result = this.asExpr().(AddExpr).asRegExp() }
}
/**
* A quoted string escape in a regular expression, using the `\q` syntax.
* The only operation supported inside a quoted string is alternation, using `|`.
*
* Example:
*
* ```
* \q{foo}
* \q{a|b|c}
* ```
*/
class RegExpQuotedString extends RegExpTerm, @regexp_quoted_string {
/** Gets the term representing the contents of this quoted string. */
RegExpTerm getTerm() { result = this.getAChild() }
override predicate isNullable() { none() }
override string getAMatchedString() { result = this.getTerm().getAMatchedString() }
override string getAPrimaryQlClass() { result = "RegExpQuotedString" }
}
module RegExp {
/** Gets the string `"?"` used to represent a regular expression whose flags are unknown. */
string unknownFlag() { result = "?" }

View File

@@ -188,27 +188,35 @@ module Routing {
)
}
/**
* Gets the path prefix needed to reach this node from the given ancestor, that is, the concatenation
* of all relative paths between this node and the ancestor.
*
* To restrict the size of the predicate, this is only available for the ancestors that are "fork" nodes,
* that is, a node that has siblings (i.e. multiple children).
*/
private string getPathFromFork(Node fork) {
private string getPathFromForkInternal(Node fork) {
this.isFork() and
this = fork and
result = ""
or
exists(Node parent | parent = this.getParent() |
not exists(parent.getRelativePath()) and
result = parent.getPathFromFork(fork)
result = parent.getPathFromForkInternal(fork)
or
result = parent.getPathFromFork(fork) + parent.getRelativePath() and
result = parent.getPathFromForkInternal(fork) + parent.getRelativePath() and
result.length() < 100
)
}
/**
* Gets the path prefix needed to reach this node from the given ancestor, that is, the concatenation
* of all relative paths between this node and the ancestor.
*
* To restrict the size of the predicate, this is only available for the ancestors that are "fork" nodes,
* that is, a node that has siblings (i.e. multiple children).
* And only a single (shortest) path is returned, even if there are multiple paths
* leading to this node.
*/
pragma[nomagic]
private string getPathFromFork(Node fork) {
result =
min(string res | res = this.getPathFromForkInternal(fork) | res order by res.length(), res)
}
/**
* Gets an HTTP method required to reach this node from the given ancestor, or `*` if any method
* can be used.

View File

@@ -773,6 +773,17 @@ class LocalTypeAccess extends @local_type_access, TypeAccess, Identifier, Lexica
*/
LocalTypeName getLocalTypeName() { result.getAnAccess() = this }
private TypeAliasDeclaration getAlias() {
this.getLocalTypeName().getADeclaration() = result.getIdentifier()
}
override TypeExpr getAnUnderlyingType() {
result = this.getAlias().getDefinition().getAnUnderlyingType()
or
not exists(this.getAlias()) and
result = this
}
override string getAPrimaryQlClass() { result = "LocalTypeAccess" }
}

View File

@@ -494,7 +494,8 @@ module TaintTracking {
succ = c and
c =
DataFlow::globalVarRef([
"encodeURI", "decodeURI", "encodeURIComponent", "decodeURIComponent"
"encodeURI", "decodeURI", "encodeURIComponent", "decodeURIComponent", "unescape",
"escape"
]).getACall() and
pred = c.getArgument(0)
)

View File

@@ -57,6 +57,16 @@ module Private {
this = getAPreciseArrayIndex().toString()
or
isAccessPathTokenPresent("Member", this)
or
this = any(ImportSpecifier spec).getImportedName()
or
this = any(ExportSpecifier n).getExportedName()
or
this = any(ExportNamedDeclaration d).getAnExportedDecl().getName()
or
this = any(MemberDefinition m).getName()
or
this = ["exports", "default"]
}
/** Gets the array index corresponding to this property name. */

View File

@@ -372,10 +372,11 @@ class CastNode extends DataFlow::Node {
cached
newtype TDataFlowCallable =
MkSourceCallable(StmtContainer container) or
MkLibraryCallable(LibraryCallable callable)
MkLibraryCallable(LibraryCallable callable) or
MkFileCallable(File file)
/**
* A callable entity. This is a wrapper around either a `StmtContainer` or a `LibraryCallable`.
* A callable entity. This is a wrapper around either a `StmtContainer`, `LibraryCallable`, or `File`.
*/
class DataFlowCallable extends TDataFlowCallable {
/** Gets a string representation of this callable. */
@@ -383,14 +384,21 @@ class DataFlowCallable extends TDataFlowCallable {
result = this.asSourceCallable().toString()
or
result = this.asLibraryCallable()
or
result = this.asFileCallable().toString()
}
/** Gets the location of this callable, if it is present in the source code. */
Location getLocation() { result = this.asSourceCallable().getLocation() }
Location getLocation() {
result = this.asSourceCallable().getLocation() or result = this.asFileCallable().getLocation()
}
/** Gets the corresponding `StmtContainer` if this is a source callable. */
StmtContainer asSourceCallable() { this = MkSourceCallable(result) }
/** Gets the corresponding `File` if this is a file representing a callable. */
File asFileCallable() { this = MkFileCallable(result) }
/** Gets the corresponding `StmtContainer` if this is a source callable. */
pragma[nomagic]
StmtContainer asSourceCallableNotExterns() {
@@ -537,6 +545,10 @@ DataFlowCallable nodeGetEnclosingCallable(Node node) {
result.asLibraryCallable() = node.(FlowSummaryDefaultExceptionalReturn).getSummarizedCallable()
or
node = TGenericSynthesizedNode(_, _, result)
or
node instanceof DataFlow::HtmlAttributeNode and result.asFileCallable() = node.getFile()
or
node instanceof DataFlow::XmlAttributeNode and result.asFileCallable() = node.getFile()
}
newtype TDataFlowType =

View File

@@ -81,7 +81,19 @@ module SsaDataflowInput implements DataFlowIntegrationInputSig {
class Guard extends js::ControlFlowNode {
Guard() { this = any(js::ConditionGuardNode g).getTest() }
predicate hasCfgNode(js::BasicBlock bb, int i) { this = bb.getNode(i) }
/**
* Holds if the control flow branching from `bb1` is dependent on this guard,
* and that the edge from `bb1` to `bb2` corresponds to the evaluation of this
* guard to `branch`.
*/
predicate controlsBranchEdge(js::BasicBlock bb1, js::BasicBlock bb2, boolean branch) {
exists(js::ConditionGuardNode g |
g.getTest() = this and
bb1 = this.getBasicBlock() and
bb2 = g.getBasicBlock() and
branch = g.getOutcome()
)
}
}
pragma[inline]
@@ -92,14 +104,6 @@ module SsaDataflowInput implements DataFlowIntegrationInputSig {
branch = g.getOutcome()
)
}
js::BasicBlock getAConditionalBasicBlockSuccessor(js::BasicBlock bb, boolean branch) {
exists(js::ConditionGuardNode g |
bb = g.getTest().getBasicBlock() and
result = g.getBasicBlock() and
branch = g.getOutcome()
)
}
}
import DataFlowIntegration<SsaDataflowInput>

View File

@@ -190,13 +190,16 @@ module Angular2 {
result.hasUnderlyingType("@angular/common/http", "HttpClient")
}
/** Gets a reference to an `HttpClient` object using the API graph. */
API::Node httpClientApiNode() { result = API::Node::ofType("@angular/common/http", "HttpClient") }
private class AngularClientRequest extends ClientRequest::Range, DataFlow::MethodCallNode {
int argumentOffset;
AngularClientRequest() {
this = httpClient().getAMethodCall("request") and argumentOffset = 1
this = httpClientApiNode().getMember("request").getACall() and argumentOffset = 1
or
this = httpClient().getAMethodCall() and
this = httpClientApiNode().getAMember().getACall() and
not this.getMethodName() = "request" and
argumentOffset = 0
}

View File

@@ -80,7 +80,7 @@ module D3 {
or
this = d3Selection().getMember("node").getReturn().asSource()
or
this = d3Selection().getMember("nodes").getReturn().getUnknownMember().asSource()
this = d3Selection().getMember("nodes").getReturn().getArrayElement().asSource()
}
}

View File

@@ -46,19 +46,6 @@ module Markdown {
}
}
/**
* A taint step for the `markdown-table` library.
*/
private class MarkdownTableStep extends MarkdownStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call | call = DataFlow::moduleImport("markdown-table").getACall() |
// TODO: needs a flow summary to ensure ArrayElement content is unfolded
succ = call and
pred = call.getArgument(0)
)
}
}
/**
* A taint step for the `showdown` library.
*/

View File

@@ -32,7 +32,7 @@ module Puppeteer {
or
result = [browser(), context()].getMember("newPage").getReturn().getPromised()
or
result = [browser(), context()].getMember("pages").getReturn().getPromised().getUnknownMember()
result = [browser(), context()].getMember("pages").getReturn().getPromised().getArrayElement()
or
result = target().getMember("page").getReturn().getPromised()
}
@@ -45,7 +45,7 @@ module Puppeteer {
or
result = [page(), browser()].getMember("target").getReturn()
or
result = context().getMember("targets").getReturn().getUnknownMember()
result = context().getMember("targets").getReturn().getArrayElement()
or
result = target().getMember("opener").getReturn()
}
@@ -58,7 +58,7 @@ module Puppeteer {
or
result = [page(), target()].getMember("browserContext").getReturn()
or
result = browser().getMember("browserContexts").getReturn().getUnknownMember()
result = browser().getMember("browserContexts").getReturn().getArrayElement()
or
result = browser().getMember("createIncognitoBrowserContext").getReturn().getPromised()
or

View File

@@ -221,7 +221,10 @@ private module Postgres {
/** Gets a value that is plugged into a raw placeholder variable, making it a sink for SQL injection. */
private DataFlow::Node getARawValue() {
result = this.getValues() and this.getARawParameterName() = "1" // Special case: if the argument is not an array or object, it's just plugged into $1
result = this.getValues() and
this.getARawParameterName() = "1" and // Special case: if the argument is not an array or object, it's just plugged into $1
not result instanceof DataFlow::ArrayCreationNode and
not result instanceof DataFlow::ObjectLiteralNode
or
exists(DataFlow::SourceNode values | values = this.getValues().getALocalSource() |
result = values.getAPropertyWrite(this.getARawParameterName()).getRhs()

View File

@@ -1,26 +0,0 @@
/**
* Provides classes and predicates modeling the Tanstack/react-query library.
*/
private import javascript
/**
* An additional flow step that propagates data from the return value of the query function,
* defined in a useQuery call from the '@tanstack/react-query' module, to the 'data' property.
*/
private class TanstackStep extends DataFlow::AdditionalFlowStep {
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
exists(API::CallNode useQuery |
useQuery = useQueryCall() and
node1 = useQuery.getParameter(0).getMember("queryFn").getReturn().getPromised().asSink() and
node2 = useQuery.getReturn().getMember("data").asSource()
)
}
}
/**
* Retrieves a call node representing a useQuery invocation from the '@tanstack/react-query' module.
*/
private API::CallNode useQueryCall() {
result = API::moduleImport("@tanstack/react-query").getMember("useQuery").getACall()
}

View File

@@ -421,3 +421,22 @@ private module ClosureLibraryUri {
}
}
}
private class QueryStringStringification extends DataFlow::SummarizedCallable {
QueryStringStringification() { this = "query-string stringification" }
override DataFlow::InvokeNode getACall() {
result =
API::moduleImport(["querystring", "query-string", "querystringify", "qs"])
.getMember("stringify")
.getACall() or
result = API::moduleImport("url-parse").getMember("qs").getMember("stringify").getACall() or
result = API::moduleImport("parseqs").getMember("encode").getACall()
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = false and
input = ["Argument[0]", "Argument[0].AnyMemberDeep"] and
output = "ReturnValue"
}
}

View File

@@ -104,8 +104,7 @@ module Vuex {
storeName = this.getNamespace() + localName
or
// mapGetters(['foo', 'bar'])
this.getLastParameter().getUnknownMember().getAValueReachingSink().getStringValue() =
localName and
this.getLastParameter().getArrayElement().getAValueReachingSink().getStringValue() = localName and
storeName = this.getNamespace() + localName
or
// mapGetters({foo: 'bar'})

View File

@@ -162,8 +162,8 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
token.getName() = "Awaited" and
result = node.getPromised()
or
token.getName() = "ArrayElement" and
result = node.getMember(DataFlow::PseudoProperties::arrayElement())
token.getName() = ["ArrayElement", "Element"] and
result = node.getArrayElement()
or
token.getName() = "Element" and
result = node.getMember(DataFlow::PseudoProperties::arrayLikeElement())
@@ -172,11 +172,6 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
token.getName() = "MapValue" and
result = node.getMember(DataFlow::PseudoProperties::mapValueAll())
or
// Currently we need to include the "unknown member" for ArrayElement and Element since
// API graphs do not use store/load steps for arrays
token.getName() = ["ArrayElement", "Element"] and
result = node.getUnknownMember()
or
token.getName() = "Parameter" and
token.getAnArgument() = "this" and
result = node.getReceiver()
@@ -373,7 +368,7 @@ bindingset[pred]
predicate apiGraphHasEdge(API::Node pred, string path, API::Node succ) {
exists(string name | succ = pred.getMember(name) and path = "Member[" + name + "]")
or
succ = pred.getUnknownMember() and path = "AnyMember"
succ = pred.getUnknownArrayElement() and path = "ArrayElement"
or
succ = pred.getInstance() and path = "Instance"
or

View File

@@ -297,13 +297,12 @@ module Stages {
exists(
API::moduleImport("foo")
.getMember("bar")
.getUnknownMember()
.getArrayElement()
.getAMember()
.getAParameter()
.getPromised()
.getReturn()
.getParameter(2)
.getUnknownMember()
.getInstance()
.getReceiver()
.getForwardingFunction()

View File

@@ -96,7 +96,8 @@ class ArrayConstructorSummary extends SummarizedCallable {
ArrayConstructorSummary() { this = "Array constructor" }
override DataFlow::InvokeNode getACallSimple() {
result = arrayConstructorRef().getAnInvocation()
result = arrayConstructorRef().getAnInvocation() and
result.getNumArgument() > 1
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {

View File

@@ -179,7 +179,6 @@ module ExternalApiUsedWithUntrustedData {
or
exists(string member |
node = base.getMember(member) and
not node = base.getUnknownMember() and
not isNumericString(member) and
not (member = "default" and base = API::moduleImport(_)) and
not member = "then" // use the 'promised' edges for .then callbacks
@@ -189,10 +188,7 @@ module ExternalApiUsedWithUntrustedData {
else result = basename + "['" + member.regexpReplaceAll("'", "\\'") + "']"
)
or
(
node = base.getUnknownMember() or
node = base.getMember(any(string s | isNumericString(s)))
) and
node = base.getArrayElement() and
result = basename + "[]"
or
// just collapse promises

View File

@@ -20,7 +20,11 @@ module ServerSideUrlRedirectConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate isBarrier(DataFlow::Node node) {
node instanceof Sanitizer
or
node = HostnameSanitizerGuard::getABarrierNode()
}
predicate isBarrierOut(DataFlow::Node node) { hostnameSanitizingPrefixEdge(node, _) }
@@ -69,10 +73,12 @@ deprecated class Configuration extends TaintTracking::Configuration {
}
/**
* DEPRECATED. This is no longer used as a sanitizer guard.
*
* A call to a function called `isLocalUrl` or similar, which is
* considered to sanitize a variable for purposes of URL redirection.
*/
class LocalUrlSanitizingGuard extends DataFlow::CallNode {
deprecated class LocalUrlSanitizingGuard extends DataFlow::CallNode {
LocalUrlSanitizingGuard() { this.getCalleeName().regexpMatch("(?i)(is_?)?local_?url") }
/** DEPRECATED. Use `blocksExpr` instead. */

View File

@@ -892,7 +892,13 @@ module TaintedPath {
TaintTracking::uriStep(node1, node2)
or
exists(DataFlow::CallNode decode |
decode.getCalleeName() = "decodeURIComponent" or decode.getCalleeName() = "decodeURI"
decode =
DataFlow::globalVarRef([
"decodeURIComponent",
"decodeURI",
"escape",
"unescape"
]).getACall()
|
node1 = decode.getArgument(0) and
node2 = decode

View File

@@ -53,7 +53,7 @@ module Shared {
class UriEncodingSanitizer extends Sanitizer, DataFlow::CallNode {
UriEncodingSanitizer() {
exists(string name | this = DataFlow::globalVarRef(name).getACall() |
name = "encodeURI" or name = "encodeURIComponent"
name in ["encodeURI", "encodeURIComponent", "escape"]
)
}
}

View File

@@ -859,7 +859,10 @@ case @regexpterm.kind of
| 24 = @regexp_char_range
| 25 = @regexp_positive_lookbehind
| 26 = @regexp_negative_lookbehind
| 27 = @regexp_unicode_property_escape;
| 27 = @regexp_unicode_property_escape
| 28 = @regexp_quoted_string
| 29 = @regexp_intersection
| 30 = @regexp_subtraction;
regexp_parse_errors (unique int id: @regexp_parse_error,
int regexp: @regexpterm ref,

View File

@@ -1194,6 +1194,18 @@
<v>12</v>
</e>
<e>
<k>@regexp_quoted_string</k>
<v>12</v>
</e>
<e>
<k>@regexp_intersection</k>
<v>12</v>
</e>
<e>
<k>@regexp_subtraction</k>
<v>12</v>
</e>
<e>
<k>@regexp_parse_error</k>
<v>122</v>
</e>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
description: Add support for quoted string, intersection and subtraction
compatibility: backwards

View File

@@ -4,14 +4,22 @@ private import codeql.util.test.InlineExpectationsTest
module Impl implements InlineExpectationsTestSig {
private import javascript
final private class LineCommentFinal = LineComment;
final class ExpectationComment = ExpectationCommentImpl;
class ExpectationComment extends LineCommentFinal {
string getContents() { result = this.getText() }
class Location = JS::Location;
abstract private class ExpectationCommentImpl extends Locatable {
abstract string getContents();
/** Gets this element's location. */
Location getLocation() { result = super.getLocation() }
}
class Location = JS::Location;
private class JSComment extends ExpectationCommentImpl instanceof Comment {
override string getContents() { result = super.getText() }
}
private class HtmlComment extends ExpectationCommentImpl instanceof HTML::CommentNode {
override string getContents() { result = super.getText() }
}
}