Merge pull request #11281 from github/tiferet/endpoint-filters

ATM: Implement the current endpoint filters as EndpointCharacteristics
This commit is contained in:
Tiferet Gazit
2022-11-29 12:38:12 -08:00
committed by GitHub
14 changed files with 499 additions and 50291 deletions

View File

@@ -6,7 +6,7 @@
private import javascript as JS
import EndpointTypes
import EndpointCharacteristics
import EndpointCharacteristics as EndpointCharacteristics
/**
* EXPERIMENTAL. This API may change in the future.
@@ -48,7 +48,7 @@ abstract class AtmConfig extends string {
final predicate isKnownSink(JS::DataFlow::Node sink) {
// If the list of characteristics includes positive indicators with maximal confidence for this class, then it's a
// known sink for the class.
exists(EndpointCharacteristic characteristic |
exists(EndpointCharacteristics::EndpointCharacteristic characteristic |
characteristic.getEndpoints(sink) and
characteristic
.getImplications(this.getASinkEndpointType(), true, characteristic.maximalConfidence())
@@ -69,7 +69,38 @@ abstract class AtmConfig extends string {
* Holds if the candidate sink `candidateSink` predicted by the machine learning model should be
* an effective sink, i.e. one considered as a possible sink of flow in the boosted query.
*/
predicate isEffectiveSink(JS::DataFlow::Node candidateSink) { none() }
predicate isEffectiveSink(JS::DataFlow::Node candidateSink) {
not exists(this.getAReasonSinkExcluded(candidateSink))
}
/**
* Gets the list of characteristics that cause `candidateSink` to be excluded as an effective sink.
*/
final EndpointCharacteristics::EndpointCharacteristic getAReasonSinkExcluded(
JS::DataFlow::Node candidateSink
) {
// An endpoint is an effective sink (sink candidate) if none of its characteristics give much indication whether or
// not it is a sink. Historically, we used endpoint filters, and scored endpoints that are filtered out neither by
// a standard endpoint filter nor by an endpoint filter specific to this sink type. To replicate this behavior, we
// have given the endpoint filter characteristics medium confidence, and we exclude endpoints that have a
// medium-confidence characteristic that indicates that they are not sinks, either in general or for this sink type.
exists(EndpointCharacteristics::EndpointCharacteristic filter, float confidence |
filter.getEndpoints(candidateSink) and
confidence >= filter.mediumConfidence() and
// TODO: Experiment with excluding all endpoints that have a medium- or high-confidence characteristic that
// implies they're not sinks, rather than using only medium-confidence characteristics, by deleting the following
// line.
confidence < filter.highConfidence() and
(
// Exclude endpoints that have a characteristic that implies they're not sinks for _any_ sink type.
filter.getImplications(any(NegativeType negative), true, confidence)
or
// Exclude endpoints that have a characteristic that implies they're not sinks for _this particular_ sink type.
filter.getImplications(this.getASinkEndpointType(), false, confidence)
) and
result = filter
)
}
/**
* EXPERIMENTAL. This API may change in the future.
@@ -85,7 +116,7 @@ abstract class AtmConfig extends string {
* Get an endpoint type for the sinks of this query. A query may have multiple applicable
* endpoint types for its sinks.
*/
EndpointType getASinkEndpointType() { none() }
abstract EndpointType getASinkEndpointType();
/**
* EXPERIMENTAL. This API may change in the future.

View File

@@ -7,6 +7,10 @@ private import semmle.javascript.security.dataflow.SqlInjectionCustomizations
private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
private import semmle.javascript.security.dataflow.NosqlInjectionCustomizations
private import semmle.javascript.security.dataflow.TaintedPathCustomizations
private import CoreKnowledge as CoreKnowledge
private import semmle.javascript.heuristics.SyntacticHeuristics as SyntacticHeuristics
private import semmle.javascript.filters.ClassifyFiles as ClassifyFiles
private import StandardEndpointFilters as StandardEndpointFilters
/**
* A set of characteristics that a particular endpoint might have. This set of characteristics is used to make decisions
@@ -135,7 +139,8 @@ private class NosqlInjectionSinkCharacteristic extends EndpointCharacteristic {
}
/*
* Characteristics that are indicative of not being a sink of any type.
* Characteristics that are indicative of not being a sink of any type, and have historically been used to select
* negative samples for training.
*/
/**
@@ -442,3 +447,420 @@ private class BuiltinCallNameCharacteristic extends ArgumentToBuiltinFunctionCha
)
}
}
/*
* Characteristics that have historically acted as endpoint filters to exclude endpoints from scoring at inference time.
*/
/** A characteristic that has historically acted as an endpoint filter for inference-time scoring. */
abstract class EndpointFilterCharacteristic extends EndpointCharacteristic {
bindingset[this]
EndpointFilterCharacteristic() { any() }
}
/**
* An EndpointFilterCharacteristic that indicates that an endpoint is unlikely to be a sink of any type.
* Replaces https://github.com/github/codeql/blob/387e57546bf7352f7c1cfe781daa1a3799b7063e/javascript/ql/experimental/adaptivethreatmodeling/lib/experimental/adaptivethreatmodeling/StandardEndpointFilters.qll#LL15C24-L15C24
*/
abstract private class StandardEndpointFilterCharacteristic extends EndpointFilterCharacteristic {
bindingset[this]
StandardEndpointFilterCharacteristic() { any() }
override predicate getImplications(
EndpointType endpointClass, boolean isPositiveIndicator, float confidence
) {
endpointClass instanceof NegativeType and
isPositiveIndicator = true and
confidence = mediumConfidence()
}
}
private class IsArgumentToModeledFunctionCharacteristic extends StandardEndpointFilterCharacteristic {
IsArgumentToModeledFunctionCharacteristic() { this = "argument to modeled function" }
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::InvokeNode invk, DataFlow::Node known |
invk.getAnArgument() = n and
invk.getAnArgument() = known and
(
CoreKnowledge::isKnownLibrarySink(known)
or
CoreKnowledge::isKnownStepSrc(known)
or
CoreKnowledge::isOtherModeledArgument(known, _)
)
)
}
}
private class IsArgumentToSinklessLibraryCharacteristic extends StandardEndpointFilterCharacteristic {
IsArgumentToSinklessLibraryCharacteristic() { this = "argument to sinkless library" }
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::InvokeNode invk, DataFlow::SourceNode commonSafeLibrary, string libraryName |
libraryName = ["slugify", "striptags", "marked"]
|
commonSafeLibrary = DataFlow::moduleImport(libraryName) and
invk = [commonSafeLibrary, commonSafeLibrary.getAPropertyRead()].getAnInvocation() and
n = invk.getAnArgument()
)
}
}
private class IsSanitizerCharacteristic extends StandardEndpointFilterCharacteristic {
IsSanitizerCharacteristic() { this = "sanitizer" }
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
call.getCalleeName().regexpMatch("(?i).*(escape|valid(ate)?|sanitize|purify).*")
)
}
}
private class IsPredicateCharacteristic extends StandardEndpointFilterCharacteristic {
IsPredicateCharacteristic() { this = "predicate" }
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
call.getCalleeName().regexpMatch("(equals|(|is|has|can)(_|[A-Z])).*")
)
}
}
private class IsHashCharacteristic extends StandardEndpointFilterCharacteristic {
IsHashCharacteristic() { this = "hash" }
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
call.getCalleeName().regexpMatch("(?i)^(sha\\d*|md5|hash)$")
)
}
}
private class IsNumericCharacteristic extends StandardEndpointFilterCharacteristic {
IsNumericCharacteristic() { this = "numeric" }
override predicate getEndpoints(DataFlow::Node n) {
SyntacticHeuristics::isReadFrom(n, ".*index.*")
}
}
private class InIrrelevantFileCharacteristic extends StandardEndpointFilterCharacteristic {
private string category;
InIrrelevantFileCharacteristic() {
this = "in " + category + " file" and category = ["externs", "generated", "library", "test"]
}
override predicate getEndpoints(DataFlow::Node n) {
// Ignore candidate sinks within externs, generated, library, and test code
ClassifyFiles::classify(n.getFile(), category)
}
}
/** An EndpointFilterCharacteristic that indicates that an endpoint is unlikely to be a NoSQL injection sink. */
abstract private class NosqlInjectionSinkEndpointFilterCharacteristic extends EndpointFilterCharacteristic {
bindingset[this]
NosqlInjectionSinkEndpointFilterCharacteristic() { any() }
override predicate getImplications(
EndpointType endpointClass, boolean isPositiveIndicator, float confidence
) {
endpointClass instanceof NosqlInjectionSinkType and
isPositiveIndicator = false and
confidence = mediumConfidence()
}
}
private class DatabaseAccessCallHeuristicCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
DatabaseAccessCallHeuristicCharacteristic() { this = "matches database access call heuristic" }
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::MethodCallNode call | n = call.getAnArgument() |
// additional databases accesses that aren't modeled yet
call.getMethodName() = ["create", "createCollection", "createIndexes"]
)
}
}
private class ModeledSinkCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
ModeledSinkCharacteristic() { this = "modeled sink" }
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
// Remove modeled sinks
CoreKnowledge::isArgumentToKnownLibrarySinkFunction(n)
)
}
}
private class PredecessorInModeledFlowStepCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
PredecessorInModeledFlowStepCharacteristic() { this = "predecessor in a modeled flow step" }
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
// Remove common kinds of unlikely sinks
CoreKnowledge::isKnownStepSrc(n)
)
}
}
private class ModeledDatabaseAccessCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
ModeledDatabaseAccessCharacteristic() { this = "modeled database access" }
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
// Remove modeled database calls. Arguments to modeled calls are very likely to be modeled
// as sinks if they are true positives. Therefore arguments that are not modeled as sinks
// are unlikely to be true positives.
call instanceof DatabaseAccess
)
}
}
private class ReceiverIsHttpRequestExpressionCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
ReceiverIsHttpRequestExpressionCharacteristic() { this = "receiver is a HTTP request expression" }
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
// Remove calls to APIs that aren't relevant to NoSQL injection
call.getReceiver() instanceof Http::RequestNode
)
}
}
private class ReceiverIsHttpResponseExpressionCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
ReceiverIsHttpResponseExpressionCharacteristic() {
this = "receiver is a HTTP response expression"
}
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
// Remove calls to APIs that aren't relevant to NoSQL injection
call.getReceiver() instanceof Http::ResponseNode
)
}
}
private class NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkNosqlCharacteristic extends NosqlInjectionSinkEndpointFilterCharacteristic {
NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkNosqlCharacteristic() {
this = "not a direct argument to a likely external library call or a heuristic sink (nosql)"
}
override predicate getEndpoints(DataFlow::Node n) {
// Require NoSQL injection sink candidates to be (a) direct arguments to external library calls
// or (b) heuristic sinks for NoSQL injection.
//
// ## Direct arguments to external library calls
//
// The `StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall` endpoint filter
// allows sink candidates which are within object literals or array literals, for example
// `req.sendFile(_, { path: ENDPOINT })`.
//
// However, the NoSQL injection query deals differently with these types of sinks compared to
// other security queries. Other security queries such as SQL injection tend to treat
// `ENDPOINT` as the ground truth sink, but the NoSQL injection query instead treats
// `{ path: ENDPOINT }` as the ground truth sink and defines an additional flow step to ensure
// data flows from `ENDPOINT` to the ground truth sink `{ path: ENDPOINT }`.
//
// Therefore for the NoSQL injection boosted query, we must ignore sink candidates within object
// literals or array literals, to avoid having multiple alerts for the same security
// vulnerability (one FP where the sink is `ENDPOINT` and one TP where the sink is
// `{ path: ENDPOINT }`). We accomplish this by directly testing that the sink candidate is an
// argument of a likely external library call.
//
// ## Heuristic sinks
//
// We also allow heuristic sinks in addition to direct arguments to external library calls.
// These are copied from the `HeuristicNosqlInjectionSink` class defined within
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
// We can't reuse the class because importing that file would cause us to treat these
// heuristic sinks as known sinks.
not n = StandardEndpointFilters::getALikelyExternalLibraryCall().getAnArgument() and
not (
SyntacticHeuristics::isAssignedToOrConcatenatedWith(n, "(?i)(nosql|query)") or
SyntacticHeuristics::isArgTo(n, "(?i)(query)")
)
}
}
/** An EndpointFilterCharacteristic that indicates that an endpoint is unlikely to be a SQL injection sink. */
abstract private class SqlInjectionSinkEndpointFilterCharacteristic extends EndpointFilterCharacteristic {
bindingset[this]
SqlInjectionSinkEndpointFilterCharacteristic() { any() }
override predicate getImplications(
EndpointType endpointClass, boolean isPositiveIndicator, float confidence
) {
endpointClass instanceof SqlInjectionSinkType and
isPositiveIndicator = false and
confidence = mediumConfidence()
}
}
private class PreparedSqlStatementCharacteristic extends SqlInjectionSinkEndpointFilterCharacteristic {
PreparedSqlStatementCharacteristic() { this = "prepared SQL statement" }
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
// prepared statements for SQL
any(DataFlow::CallNode cn | cn.getCalleeName() = "prepare")
.getAMethodCall("run")
.getAnArgument() = n
)
}
}
private class ArrayCreationCharacteristic extends SqlInjectionSinkEndpointFilterCharacteristic {
ArrayCreationCharacteristic() { this = "array creation" }
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
n instanceof DataFlow::ArrayCreationNode
)
}
}
private class HtmlOrRenderingCharacteristic extends SqlInjectionSinkEndpointFilterCharacteristic {
HtmlOrRenderingCharacteristic() { this = "HTML / rendering" }
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
// UI is unrelated to SQL
call.getCalleeName().regexpMatch("(?i).*(render|html).*")
)
}
}
private class NotAnArgumentToLikelyExternalLibraryCallOrHeuristicSinkCharacteristic extends SqlInjectionSinkEndpointFilterCharacteristic {
NotAnArgumentToLikelyExternalLibraryCallOrHeuristicSinkCharacteristic() {
this = "not an argument to a likely external library call or a heuristic sink"
}
override predicate getEndpoints(DataFlow::Node n) {
// Require SQL injection sink candidates to be (a) arguments to external library calls
// (possibly indirectly), or (b) heuristic sinks.
//
// Heuristic sinks are copied from the `HeuristicSqlInjectionSink` class defined within
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
// We can't reuse the class because importing that file would cause us to treat these
// heuristic sinks as known sinks.
not StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall(n) and
not (
SyntacticHeuristics::isAssignedToOrConcatenatedWith(n, "(?i)(sql|query)") or
SyntacticHeuristics::isArgTo(n, "(?i)(query)") or
SyntacticHeuristics::isConcatenatedWithString(n,
"(?s).*(ALTER|COUNT|CREATE|DATABASE|DELETE|DISTINCT|DROP|FROM|GROUP|INSERT|INTO|LIMIT|ORDER|SELECT|TABLE|UPDATE|WHERE).*")
)
}
}
/** An EndpointFilterCharacteristic that indicates that an endpoint is unlikely to be a tainted path injection sink. */
abstract private class TaintedPathSinkEndpointFilterCharacteristic extends EndpointFilterCharacteristic {
bindingset[this]
TaintedPathSinkEndpointFilterCharacteristic() { any() }
override predicate getImplications(
EndpointType endpointClass, boolean isPositiveIndicator, float confidence
) {
endpointClass instanceof TaintedPathSinkType and
isPositiveIndicator = false and
confidence = mediumConfidence()
}
}
private class NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkTaintedPathCharacteristic extends TaintedPathSinkEndpointFilterCharacteristic {
NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkTaintedPathCharacteristic() {
this =
"not a direct argument to a likely external library call or a heuristic sink (tainted path)"
}
override predicate getEndpoints(DataFlow::Node n) {
// Require path injection sink candidates to be (a) arguments to external library calls
// (possibly indirectly), or (b) heuristic sinks.
//
// Heuristic sinks are mostly copied from the `HeuristicTaintedPathSink` class defined within
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
// We can't reuse the class because importing that file would cause us to treat these
// heuristic sinks as known sinks.
not StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall(n) and
not (
SyntacticHeuristics::isAssignedToOrConcatenatedWith(n, "(?i)(file|folder|dir|absolute)")
or
SyntacticHeuristics::isArgTo(n, "(?i)(get|read)file")
or
exists(string pathPattern |
// paths with at least two parts, and either a trailing or leading slash
pathPattern = "(?i)([a-z0-9_.-]+/){2,}" or
pathPattern = "(?i)(/[a-z0-9_.-]+){2,}"
|
SyntacticHeuristics::isConcatenatedWithString(n, pathPattern)
)
or
SyntacticHeuristics::isConcatenatedWithStrings(".*/", n, "/.*")
or
// In addition to the names from `HeuristicTaintedPathSink` in the
// `isAssignedToOrConcatenatedWith` predicate call above, we also allow the noisier "path"
// name.
SyntacticHeuristics::isAssignedToOrConcatenatedWith(n, "(?i)path")
)
}
}
/** An EndpointFilterCharacteristic that indicates that an endpoint is unlikely to be an XSS sink. */
abstract private class XssSinkEndpointFilterCharacteristic extends EndpointFilterCharacteristic {
bindingset[this]
XssSinkEndpointFilterCharacteristic() { any() }
override predicate getImplications(
EndpointType endpointClass, boolean isPositiveIndicator, float confidence
) {
endpointClass instanceof XssSinkType and
isPositiveIndicator = false and
confidence = mediumConfidence()
}
}
private class SetStateCallsInReactApplicationsCharacteristic extends XssSinkEndpointFilterCharacteristic {
SetStateCallsInReactApplicationsCharacteristic() {
this = "setState calls ought to be safe in react applications"
}
override predicate getEndpoints(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() | call.getCalleeName() = "setState")
}
}
private class NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkXssCharacteristic extends XssSinkEndpointFilterCharacteristic {
NotDirectArgumentToLikelyExternalLibraryCallOrHeuristicSinkXssCharacteristic() {
this = "not a direct argument to a likely external library call or a heuristic sink (xss)"
}
override predicate getEndpoints(DataFlow::Node n) {
// Require XSS sink candidates to be (a) arguments to external library calls (possibly
// indirectly), or (b) heuristic sinks.
//
// Heuristic sinks are copied from the `HeuristicDomBasedXssSink` class defined within
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
// We can't reuse the class because importing that file would cause us to treat these
// heuristic sinks as known sinks.
not StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall(n) and
not (
SyntacticHeuristics::isAssignedToOrConcatenatedWith(n, "(?i)(html|innerhtml)")
or
SyntacticHeuristics::isArgTo(n, "(?i)(html|render)")
or
n instanceof StringOps::HtmlConcatenationLeaf
or
SyntacticHeuristics::isConcatenatedWithStrings("(?is).*<[a-z ]+.*", n, "(?s).*>.*")
or
// In addition to the heuristic sinks from `HeuristicDomBasedXssSink`, explicitly allow
// property writes like `elem.innerHTML = <TAINT>` that may not be picked up as HTML
// concatenation leaves.
exists(DataFlow::PropWrite pw |
pw.getPropertyName().regexpMatch("(?i).*html*") and
pw.getRhs() = n
)
)
}
}

View File

@@ -9,82 +9,6 @@ private import semmle.javascript.heuristics.SyntacticHeuristics
private import semmle.javascript.security.dataflow.NosqlInjectionCustomizations
import AdaptiveThreatModeling
private import CoreKnowledge as CoreKnowledge
private import StandardEndpointFilters as StandardEndpointFilters
module SinkEndpointFilter {
/**
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
*
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
* effective sink.
*/
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate)
or
exists(DataFlow::CallNode call | sinkCandidate = call.getAnArgument() |
// additional databases accesses that aren't modeled yet
call.(DataFlow::MethodCallNode).getMethodName() =
["create", "createCollection", "createIndexes"] and
result = "matches database access call heuristic"
or
// Remove modeled sinks
CoreKnowledge::isArgumentToKnownLibrarySinkFunction(sinkCandidate) and
result = "modeled sink"
or
// Remove common kinds of unlikely sinks
CoreKnowledge::isKnownStepSrc(sinkCandidate) and
result = "predecessor in a modeled flow step"
or
// Remove modeled database calls. Arguments to modeled calls are very likely to be modeled
// as sinks if they are true positives. Therefore arguments that are not modeled as sinks
// are unlikely to be true positives.
call instanceof DatabaseAccess and
result = "modeled database access"
or
// Remove calls to APIs that aren't relevant to NoSQL injection
call.getReceiver() instanceof Http::RequestNode and
result = "receiver is a HTTP request expression"
or
call.getReceiver() instanceof Http::ResponseNode and
result = "receiver is a HTTP response expression"
)
or
// Require NoSQL injection sink candidates to be (a) direct arguments to external library calls
// or (b) heuristic sinks for NoSQL injection.
//
// ## Direct arguments to external library calls
//
// The `StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall` endpoint filter
// allows sink candidates which are within object literals or array literals, for example
// `req.sendFile(_, { path: ENDPOINT })`.
//
// However, the NoSQL injection query deals differently with these types of sinks compared to
// other security queries. Other security queries such as SQL injection tend to treat
// `ENDPOINT` as the ground truth sink, but the NoSQL injection query instead treats
// `{ path: ENDPOINT }` as the ground truth sink and defines an additional flow step to ensure
// data flows from `ENDPOINT` to the ground truth sink `{ path: ENDPOINT }`.
//
// Therefore for the NoSQL injection boosted query, we must ignore sink candidates within object
// literals or array literals, to avoid having multiple alerts for the same security
// vulnerability (one FP where the sink is `ENDPOINT` and one TP where the sink is
// `{ path: ENDPOINT }`). We accomplish this by directly testing that the sink candidate is an
// argument of a likely external library call.
//
// ## Heuristic sinks
//
// We also allow heuristic sinks in addition to direct arguments to external library calls.
// These are copied from the `HeuristicNosqlInjectionSink` class defined within
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
// We can't reuse the class because importing that file would cause us to treat these
// heuristic sinks as known sinks.
not sinkCandidate = StandardEndpointFilters::getALikelyExternalLibraryCall().getAnArgument() and
not (
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)(nosql|query)") or
isArgTo(sinkCandidate, "(?i)(query)")
) and
result = "not a direct argument to a likely external library call or a heuristic sink"
}
}
class NosqlInjectionAtmConfig extends AtmConfig {
NosqlInjectionAtmConfig() { this = "NosqlInjectionATMConfig" }
@@ -93,10 +17,6 @@ class NosqlInjectionAtmConfig extends AtmConfig {
source instanceof NosqlInjection::Source or TaintedObject::isSource(source, _)
}
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
}
override EndpointType getASinkEndpointType() { result instanceof NosqlInjectionSinkType }
}

View File

@@ -8,67 +8,12 @@ import semmle.javascript.heuristics.SyntacticHeuristics
import semmle.javascript.security.dataflow.SqlInjectionCustomizations
import AdaptiveThreatModeling
import CoreKnowledge as CoreKnowledge
import StandardEndpointFilters as StandardEndpointFilters
/**
* This module provides logic to filter candidate sinks to those which are likely SQL injection
* sinks.
*/
module SinkEndpointFilter {
private import javascript
private import SQL
/**
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
*
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
* effective sink.
*/
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate)
or
exists(DataFlow::CallNode call | sinkCandidate = call.getAnArgument() |
// prepared statements for SQL
any(DataFlow::CallNode cn | cn.getCalleeName() = "prepare")
.getAMethodCall("run")
.getAnArgument() = sinkCandidate and
result = "prepared SQL statement"
or
sinkCandidate instanceof DataFlow::ArrayCreationNode and
result = "array creation"
or
// UI is unrelated to SQL
call.getCalleeName().regexpMatch("(?i).*(render|html).*") and
result = "HTML / rendering"
)
or
// Require SQL injection sink candidates to be (a) arguments to external library calls
// (possibly indirectly), or (b) heuristic sinks.
//
// Heuristic sinks are copied from the `HeuristicSqlInjectionSink` class defined within
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
// We can't reuse the class because importing that file would cause us to treat these
// heuristic sinks as known sinks.
not StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall(sinkCandidate) and
not (
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)(sql|query)") or
isArgTo(sinkCandidate, "(?i)(query)") or
isConcatenatedWithString(sinkCandidate,
"(?s).*(ALTER|COUNT|CREATE|DATABASE|DELETE|DISTINCT|DROP|FROM|GROUP|INSERT|INTO|LIMIT|ORDER|SELECT|TABLE|UPDATE|WHERE).*")
) and
result = "not an argument to a likely external library call or a heuristic sink"
}
}
class SqlInjectionAtmConfig extends AtmConfig {
SqlInjectionAtmConfig() { this = "SqlInjectionATMConfig" }
override predicate isKnownSource(DataFlow::Node source) { source instanceof SqlInjection::Source }
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
}
override EndpointType getASinkEndpointType() { result instanceof SqlInjectionSinkType }
}

View File

@@ -10,27 +10,7 @@ private import javascript
private import semmle.javascript.filters.ClassifyFiles as ClassifyFiles
private import semmle.javascript.heuristics.SyntacticHeuristics
private import CoreKnowledge as CoreKnowledge
/** Provides a set of reasons why a given data flow node should be excluded as a sink candidate. */
string getAReasonSinkExcluded(DataFlow::Node n) {
isArgumentToModeledFunction(n) and result = "argument to modeled function"
or
isArgumentToSinklessLibrary(n) and result = "argument to sinkless library"
or
isSanitizer(n) and result = "sanitizer"
or
isPredicate(n) and result = "predicate"
or
isHash(n) and result = "hash"
or
isNumeric(n) and result = "numeric"
or
// Ignore candidate sinks within externs, generated, library, and test code
exists(string category | category = ["externs", "generated", "library", "test"] |
ClassifyFiles::classify(n.getFile(), category) and
result = "in " + category + " file"
)
}
import EndpointCharacteristics as EndpointCharacteristics
/**
* Holds if the node `n` is an argument to a function that has a manual model.
@@ -50,42 +30,6 @@ predicate isSomeModeledArgument(DataFlow::Node n) {
CoreKnowledge::isOtherModeledArgument(n, _)
}
/**
* Holds if `n` appears to be a numeric value.
*/
predicate isNumeric(DataFlow::Node n) { isReadFrom(n, ".*index.*") }
/**
* Holds if `n` is an argument to a library without sinks.
*/
predicate isArgumentToSinklessLibrary(DataFlow::Node n) {
exists(DataFlow::InvokeNode invk, DataFlow::SourceNode commonSafeLibrary, string libraryName |
libraryName = ["slugify", "striptags", "marked"]
|
commonSafeLibrary = DataFlow::moduleImport(libraryName) and
invk = [commonSafeLibrary, commonSafeLibrary.getAPropertyRead()].getAnInvocation() and
n = invk.getAnArgument()
)
}
predicate isSanitizer(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
call.getCalleeName().regexpMatch("(?i).*(escape|valid(ate)?|sanitize|purify).*")
)
}
predicate isPredicate(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
call.getCalleeName().regexpMatch("(equals|(|is|has|can)(_|[A-Z])).*")
)
}
predicate isHash(DataFlow::Node n) {
exists(DataFlow::CallNode call | n = call.getAnArgument() |
call.getCalleeName().regexpMatch("(?i)^(sha\\d*|md5|hash)$")
)
}
/**
* Holds if the data flow node is a (possibly indirect) argument of a likely external library call.
*
@@ -125,7 +69,7 @@ private DataFlow::SourceNode getACallback(DataFlow::ParameterNode p, DataFlow::T
* Get calls for which we do not have the callee (i.e. the definition of the called function). This
* acts as a heuristic for identifying calls to external library functions.
*/
private DataFlow::CallNode getACallWithoutCallee() {
DataFlow::CallNode getACallWithoutCallee() {
forall(Function callee | callee = result.getACallee() | callee.getTopLevel().isExterns()) and
not exists(DataFlow::ParameterNode param, DataFlow::FunctionNode callback |
param.flowsTo(result.getCalleeNode()) and

View File

@@ -8,66 +8,12 @@ import semmle.javascript.heuristics.SyntacticHeuristics
import semmle.javascript.security.dataflow.TaintedPathCustomizations
import AdaptiveThreatModeling
import CoreKnowledge as CoreKnowledge
import StandardEndpointFilters as StandardEndpointFilters
/**
* This module provides logic to filter candidate sinks to those which are likely path injection
* sinks.
*/
module SinkEndpointFilter {
private import javascript
private import TaintedPath
/**
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
*
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
* effective sink.
*/
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate)
or
// Require path injection sink candidates to be (a) arguments to external library calls
// (possibly indirectly), or (b) heuristic sinks.
//
// Heuristic sinks are mostly copied from the `HeuristicTaintedPathSink` class defined within
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
// We can't reuse the class because importing that file would cause us to treat these
// heuristic sinks as known sinks.
not StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall(sinkCandidate) and
not (
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)(file|folder|dir|absolute)")
or
isArgTo(sinkCandidate, "(?i)(get|read)file")
or
exists(string pathPattern |
// paths with at least two parts, and either a trailing or leading slash
pathPattern = "(?i)([a-z0-9_.-]+/){2,}" or
pathPattern = "(?i)(/[a-z0-9_.-]+){2,}"
|
isConcatenatedWithString(sinkCandidate, pathPattern)
)
or
isConcatenatedWithStrings(".*/", sinkCandidate, "/.*")
or
// In addition to the names from `HeuristicTaintedPathSink` in the
// `isAssignedToOrConcatenatedWith` predicate call above, we also allow the noisier "path"
// name.
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)path")
) and
result = "not a direct argument to a likely external library call or a heuristic sink"
}
}
class TaintedPathAtmConfig extends AtmConfig {
TaintedPathAtmConfig() { this = "TaintedPathATMConfig" }
override predicate isKnownSource(DataFlow::Node source) { source instanceof TaintedPath::Source }
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
}
override EndpointType getASinkEndpointType() { result instanceof TaintedPathSinkType }
}

View File

@@ -8,67 +8,12 @@ private import semmle.javascript.heuristics.SyntacticHeuristics
private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
import AdaptiveThreatModeling
import CoreKnowledge as CoreKnowledge
import StandardEndpointFilters as StandardEndpointFilters
/**
* This module provides logic to filter candidate sinks to those which are likely XSS sinks.
*/
module SinkEndpointFilter {
private import javascript
private import DomBasedXss
/**
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
*
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
* effective sink.
*/
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate)
or
exists(DataFlow::CallNode call | sinkCandidate = call.getAnArgument() |
call.getCalleeName() = "setState"
) and
result = "setState calls ought to be safe in react applications"
or
// Require XSS sink candidates to be (a) arguments to external library calls (possibly
// indirectly), or (b) heuristic sinks.
//
// Heuristic sinks are copied from the `HeuristicDomBasedXssSink` class defined within
// `codeql/javascript/ql/src/semmle/javascript/heuristics/AdditionalSinks.qll`.
// We can't reuse the class because importing that file would cause us to treat these
// heuristic sinks as known sinks.
not StandardEndpointFilters::flowsToArgumentOfLikelyExternalLibraryCall(sinkCandidate) and
not (
isAssignedToOrConcatenatedWith(sinkCandidate, "(?i)(html|innerhtml)")
or
isArgTo(sinkCandidate, "(?i)(html|render)")
or
sinkCandidate instanceof StringOps::HtmlConcatenationLeaf
or
isConcatenatedWithStrings("(?is).*<[a-z ]+.*", sinkCandidate, "(?s).*>.*")
or
// In addition to the heuristic sinks from `HeuristicDomBasedXssSink`, explicitly allow
// property writes like `elem.innerHTML = <TAINT>` that may not be picked up as HTML
// concatenation leaves.
exists(DataFlow::PropWrite pw |
pw.getPropertyName().regexpMatch("(?i).*html*") and
pw.getRhs() = sinkCandidate
)
) and
result = "not a direct argument to a likely external library call or a heuristic sink"
}
}
class DomBasedXssAtmConfig extends AtmConfig {
DomBasedXssAtmConfig() { this = "DomBasedXssATMConfig" }
override predicate isKnownSource(DataFlow::Node source) { source instanceof DomBasedXss::Source }
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
}
override EndpointType getASinkEndpointType() { result instanceof XssSinkType }
}

View File

@@ -12,19 +12,23 @@
import javascript
import experimental.adaptivethreatmodeling.ATMConfig
import extraction.ExtractEndpointDataTraining
private import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
private import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
private import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
private import experimental.adaptivethreatmodeling.XssATM as XssAtm
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate, Query query) {
query instanceof NosqlInjectionQuery and
result = NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
result = any(NosqlInjectionAtm::NosqlInjectionAtmConfig cfg).getAReasonSinkExcluded(sinkCandidate)
or
query instanceof SqlInjectionQuery and
result = SqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
result = any(SqlInjectionAtm::SqlInjectionAtmConfig cfg).getAReasonSinkExcluded(sinkCandidate)
or
query instanceof TaintedPathQuery and
result = TaintedPathAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
result = any(TaintedPathAtm::TaintedPathAtmConfig cfg).getAReasonSinkExcluded(sinkCandidate)
or
query instanceof XssQuery and
result = XssAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
result = any(XssAtm::DomBasedXssAtmConfig cfg).getAReasonSinkExcluded(sinkCandidate)
}
pragma[inline]

View File

@@ -10,10 +10,10 @@ import experimental.adaptivethreatmodeling.EndpointFeatures as EndpointFeatures
import NoFeaturizationRestrictionsConfig
private import Exclusions as Exclusions
import Queries
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
import experimental.adaptivethreatmodeling.XssATM as XssAtm
private import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
private import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
private import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
private import experimental.adaptivethreatmodeling.XssATM as XssAtm
/**
* Gets the set of featureName-featureValue pairs for each endpoint in the training set.
@@ -73,6 +73,10 @@ query predicate trainingEndpoints(
c instanceof LikelyNotASinkCharacteristic
)
) and
// Don't surface endpoint filters as characteristics, because they were previously not surfaced.
// TODO: Experiment with surfacing these to the modeling code by removing the following line (and then make
// EndpointFilterCharacteristic private).
not characteristic instanceof EndpointFilterCharacteristic and
(
// If the list of characteristics includes positive indicators with high confidence for this class, select this as a
// training sample belonging to the class.
@@ -188,6 +192,10 @@ query predicate reformattedTrainingEndpoints(
confidence3 >= characteristic3.getHighConfidenceThreshold() and
not posClass instanceof NegativeType
) and
// Don't surface endpoint filters as notASinkReasons, because they were previously not surfaced.
// TODO: Experiment with surfacing these to the modeling code by removing the following line (and then make
// EndpointFilterCharacteristic private).
not value instanceof EndpointFilterCharacteristic and
valueType = "string"
)
)

View File

@@ -17,10 +17,10 @@ import extraction.NoFeaturizationRestrictionsConfig
query predicate tokenFeatures(DataFlow::Node endpoint, string featureName, string featureValue) {
(
not exists(NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(SqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(TaintedPathAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(XssAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(any(NosqlInjectionAtm::NosqlInjectionAtmConfig cfg).getAReasonSinkExcluded(endpoint)) or
not exists(any(SqlInjectionAtm::SqlInjectionAtmConfig cfg).getAReasonSinkExcluded(endpoint)) or
not exists(any(TaintedPathAtm::TaintedPathAtmConfig cfg).getAReasonSinkExcluded(endpoint)) or
not exists(any(XssAtm::DomBasedXssAtmConfig cfg).getAReasonSinkExcluded(endpoint)) or
StandardEndpointFilters::isArgumentToModeledFunction(endpoint)
) and
EndpointFeatures::tokenFeatures(endpoint, featureName, featureValue)

View File

@@ -1,16 +1,16 @@
nosqlFilteredTruePositives
| autogenerated/NosqlAndSqlInjection/untyped/mongoose.js:111:14:111:18 | query | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/NosqlAndSqlInjection/untyped/mongoose.js:111:14:111:18 | query | not a direct argument to a likely external library call or a heuristic sink (nosql) |
sqlFilteredTruePositives
| autogenerated/NosqlAndSqlInjection/untyped/tst2.js:7:13:7:45 | select ... e id = | not an argument to a likely external library call or a heuristic sink |
| autogenerated/NosqlAndSqlInjection/untyped/tst2.js:7:48:7:60 | req.params.id | not an argument to a likely external library call or a heuristic sink |
taintedPathFilteredTruePositives
| autogenerated/TaintedPath/TaintedPath.js:66:26:66:31 | "SAFE" | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/TaintedPath/TaintedPath.js:71:26:71:45 | Cookie.get("unsafe") | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/TaintedPath/TaintedPath.js:66:26:66:31 | "SAFE" | not a direct argument to a likely external library call or a heuristic sink (tainted path) |
| autogenerated/TaintedPath/TaintedPath.js:71:26:71:45 | Cookie.get("unsafe") | not a direct argument to a likely external library call or a heuristic sink (tainted path) |
xssFilteredTruePositives
| autogenerated/Xss/DomBasedXss/d3.js:12:20:12:29 | getTaint() | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/Xss/DomBasedXss/d3.js:14:20:14:29 | getTaint() | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/Xss/DomBasedXss/express.js:7:15:7:33 | req.param("wobble") | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/Xss/DomBasedXss/jwt-server.js:11:19:11:29 | decoded.foo | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/Xss/DomBasedXss/tst.js:316:35:316:42 | location | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/Xss/DomBasedXss/typeahead.js:10:16:10:18 | loc | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/Xss/DomBasedXss/typeahead.js:25:18:25:20 | val | not a direct argument to a likely external library call or a heuristic sink |
| autogenerated/Xss/DomBasedXss/d3.js:12:20:12:29 | getTaint() | not a direct argument to a likely external library call or a heuristic sink (xss) |
| autogenerated/Xss/DomBasedXss/d3.js:14:20:14:29 | getTaint() | not a direct argument to a likely external library call or a heuristic sink (xss) |
| autogenerated/Xss/DomBasedXss/express.js:7:15:7:33 | req.param("wobble") | not a direct argument to a likely external library call or a heuristic sink (xss) |
| autogenerated/Xss/DomBasedXss/jwt-server.js:11:19:11:29 | decoded.foo | not a direct argument to a likely external library call or a heuristic sink (xss) |
| autogenerated/Xss/DomBasedXss/tst.js:316:35:316:42 | location | not a direct argument to a likely external library call or a heuristic sink (xss) |
| autogenerated/Xss/DomBasedXss/typeahead.js:10:16:10:18 | loc | not a direct argument to a likely external library call or a heuristic sink (xss) |
| autogenerated/Xss/DomBasedXss/typeahead.js:25:18:25:20 | val | not a direct argument to a likely external library call or a heuristic sink (xss) |

View File

@@ -16,7 +16,6 @@ import semmle.javascript.security.dataflow.NosqlInjectionCustomizations
import semmle.javascript.security.dataflow.SqlInjectionCustomizations
import semmle.javascript.security.dataflow.TaintedPathCustomizations
import semmle.javascript.security.dataflow.DomBasedXssCustomizations
import experimental.adaptivethreatmodeling.StandardEndpointFilters as StandardEndpointFilters
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
@@ -24,24 +23,24 @@ import experimental.adaptivethreatmodeling.XssATM as XssAtm
query predicate nosqlFilteredTruePositives(DataFlow::Node endpoint, string reason) {
endpoint instanceof NosqlInjection::Sink and
reason = NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason = any(NosqlInjectionAtm::NosqlInjectionAtmConfig cfg).getAReasonSinkExcluded(endpoint) and
not reason = ["argument to modeled function", "modeled sink", "modeled database access"]
}
query predicate sqlFilteredTruePositives(DataFlow::Node endpoint, string reason) {
endpoint instanceof SqlInjection::Sink and
reason = SqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason = any(SqlInjectionAtm::SqlInjectionAtmConfig cfg).getAReasonSinkExcluded(endpoint) and
reason != "argument to modeled function"
}
query predicate taintedPathFilteredTruePositives(DataFlow::Node endpoint, string reason) {
endpoint instanceof TaintedPath::Sink and
reason = TaintedPathAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason = any(TaintedPathAtm::TaintedPathAtmConfig cfg).getAReasonSinkExcluded(endpoint) and
reason != "argument to modeled function"
}
query predicate xssFilteredTruePositives(DataFlow::Node endpoint, string reason) {
endpoint instanceof DomBasedXss::Sink and
reason = XssAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason = any(XssAtm::DomBasedXssAtmConfig cfg).getAReasonSinkExcluded(endpoint) and
reason != "argument to modeled function"
}

View File

@@ -2,5 +2,5 @@ import javascript
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
query predicate effectiveSinks(DataFlow::Node node) {
not exists(NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(node))
not exists(any(NosqlInjectionAtm::NosqlInjectionAtmConfig cfg).getAReasonSinkExcluded(node))
}