From 5ed362f7d63040c05da49304142b0479b753e7d5 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 4 Nov 2024 16:22:09 +0100 Subject: [PATCH 01/19] JS: Add exception test case --- .../library-tests/TripleDot/exceptions.js | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 javascript/ql/test/library-tests/TripleDot/exceptions.js diff --git a/javascript/ql/test/library-tests/TripleDot/exceptions.js b/javascript/ql/test/library-tests/TripleDot/exceptions.js new file mode 100644 index 000000000000..e52dda7500a7 --- /dev/null +++ b/javascript/ql/test/library-tests/TripleDot/exceptions.js @@ -0,0 +1,65 @@ +import 'dummy'; + +function e1() { + let array = [source('e1.1')]; + try { + array.forEach(x => { + throw x; + }); + array.forEach(x => { + throw source('e1.2'); + }); + } catch (err) { + sink(err); // $ hasValueFlow=e1.2 hasValueFlow=e1.1 + } +} + +function e2() { + let array = [source('e2.1')]; + try { + array.unknown(x => { + throw x; + }); + array.unknown(x => { + throw source('e2.2'); + }); + } catch (err) { + sink(err); // $ MISSING: hasValueFlow=e2.2 + } +} + +function e3() { + const events = getSomething(); + try { + events.addEventListener('click', () =>{ + throw source('e3.1'); + }); + events.addListener('click', () =>{ + throw source('e3.2'); + }); + events.on('click', () =>{ + throw source('e3.3'); + }); + events.unknownMethod('click', () =>{ + throw source('e3.4'); + }); + } catch (err) { + sink(err); // $ MISSING: hasValueFlow=e3.4 + } +} + +function e4() { + function thrower(array) { + array.forEach(x => { throw x }); + } + try { + thrower([source("e4.1")]); + } catch (e) { + sink(e); // $ hasValueFlow=e4.1 + } + try { + thrower(["safe"]); + } catch (e) { + sink(e); // $ SPURIOUS: hasValueFlow=e4.1 + } +} From 7acc5689cfdf16a438a29eea171128aca2a44eaa Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 22 Aug 2024 14:00:09 +0200 Subject: [PATCH 02/19] JS: Port exception steps to a universal summary --- .../lib/semmle/javascript/StandardLibrary.qll | 2 +- .../flow_summaries/AllFlowSummaries.qll | 1 + .../internal/flow_summaries/ExceptionFlow.qll | 32 +++++++++++++++++++ .../internal/flow_summaries/Promises.qll | 2 +- .../library-tests/TripleDot/exceptions.js | 8 ++--- 5 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll diff --git a/javascript/ql/lib/semmle/javascript/StandardLibrary.qll b/javascript/ql/lib/semmle/javascript/StandardLibrary.qll index dc856fbab4bf..a9102577bc27 100644 --- a/javascript/ql/lib/semmle/javascript/StandardLibrary.qll +++ b/javascript/ql/lib/semmle/javascript/StandardLibrary.qll @@ -69,7 +69,7 @@ private class ArrayIterationCallbackAsPartialInvoke extends DataFlow::PartialInv * A flow step propagating the exception thrown from a callback to a method whose name coincides * a built-in Array iteration method, such as `forEach` or `map`. */ -private class IteratorExceptionStep extends DataFlow::SharedFlowStep { +private class IteratorExceptionStep extends DataFlow::LegacyFlowStep { override predicate step(DataFlow::Node pred, DataFlow::Node succ) { exists(DataFlow::MethodCallNode call | call.getMethodName() = ["forEach", "each", "map", "filter", "some", "every", "fold", "reduce"] and diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/AllFlowSummaries.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/AllFlowSummaries.qll index 6094141cc407..5935fa8bfd60 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/AllFlowSummaries.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/AllFlowSummaries.qll @@ -1,6 +1,7 @@ private import AmbiguousCoreMethods private import Arrays private import AsyncAwait +private import ExceptionFlow private import ForOfLoops private import Generators private import Iterators diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll new file mode 100644 index 000000000000..aee3be8a95b6 --- /dev/null +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll @@ -0,0 +1,32 @@ +/** + * Contains a summary for propagating exceptions out of callbacks + */ + +private import javascript +private import FlowSummaryUtil +private import semmle.javascript.dataflow.internal.AdditionalFlowInternal +private import semmle.javascript.dataflow.FlowSummary +private import semmle.javascript.internal.flow_summaries.Promises + +/** + * Summary that propagates exceptions out of callbacks back to the caller. + */ +private class ExceptionFlowSummary extends SummarizedCallable { + ExceptionFlowSummary() { this = "Exception propagator" } + + override DataFlow::CallNode getACall() { + not exists(result.getACallee()) and + // Avoid a few common cases where the exception should not propagate back + not result.getCalleeName() = + ["then", "catch", "finally", "addEventListener", EventEmitter::on()] and + not result = promiseConstructorRef().getAnInvocation() and + // Restrict to cases where a callback is known to flow in, as lambda flow in DataFlowImplCommon blows up otherwise + exists(result.getABoundCallbackParameter(_, _)) + } + + override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { + preservesValue = true and + input = "Argument[0..].ReturnValue[exception]" and + output = "ReturnValue[exception]" + } +} diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll index fb2f05f17b79..aa9398f14bb3 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll @@ -6,7 +6,7 @@ private import javascript private import semmle.javascript.dataflow.FlowSummary private import FlowSummaryUtil -private DataFlow::SourceNode promiseConstructorRef() { +DataFlow::SourceNode promiseConstructorRef() { result = Promises::promiseConstructorRef() or result = DataFlow::moduleImport("bluebird") diff --git a/javascript/ql/test/library-tests/TripleDot/exceptions.js b/javascript/ql/test/library-tests/TripleDot/exceptions.js index e52dda7500a7..dddd53a046ec 100644 --- a/javascript/ql/test/library-tests/TripleDot/exceptions.js +++ b/javascript/ql/test/library-tests/TripleDot/exceptions.js @@ -10,7 +10,7 @@ function e1() { throw source('e1.2'); }); } catch (err) { - sink(err); // $ hasValueFlow=e1.2 hasValueFlow=e1.1 + sink(err); // $ hasValueFlow=e1.2 MISSING: hasValueFlow=e1.1 } } @@ -24,7 +24,7 @@ function e2() { throw source('e2.2'); }); } catch (err) { - sink(err); // $ MISSING: hasValueFlow=e2.2 + sink(err); // $ hasValueFlow=e2.2 } } @@ -55,11 +55,11 @@ function e4() { try { thrower([source("e4.1")]); } catch (e) { - sink(e); // $ hasValueFlow=e4.1 + sink(e); // $ MISSING: hasValueFlow=e4.1 } try { thrower(["safe"]); } catch (e) { - sink(e); // $ SPURIOUS: hasValueFlow=e4.1 + sink(e); } } From 7f2eae0966b01a633e8ffb5fedc597a6abed4112 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 18 Nov 2024 13:34:35 +0100 Subject: [PATCH 03/19] JS: Add test case for false flow through IIFEs We generate local flow steps into and out of IIFEs, but these come jump steps automatically, resulting in FPs. --- .../ql/test/library-tests/TripleDot/iife.js | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 javascript/ql/test/library-tests/TripleDot/iife.js diff --git a/javascript/ql/test/library-tests/TripleDot/iife.js b/javascript/ql/test/library-tests/TripleDot/iife.js new file mode 100644 index 000000000000..8da09f67c0ab --- /dev/null +++ b/javascript/ql/test/library-tests/TripleDot/iife.js @@ -0,0 +1,43 @@ +function f1() { + function inner(x) { + return (function(p) { + return p; // argument to return + })(x); + } + sink(inner(source("f1.1"))); // $ hasValueFlow=f1.1 SPURIOUS: hasValueFlow=f1.2 + sink(inner(source("f1.2"))); // $ hasValueFlow=f1.2 SPURIOUS: hasValueFlow=f1.1 +} + +function f2() { + function inner(x) { + let y; + (function(p) { + y = p; // parameter to captured variable + })(x); + return y; + } + sink(inner(source("f2.1"))); // $ hasValueFlow=f2.1 SPURIOUS: hasValueFlow=f2.2 + sink(inner(source("f2.2"))); // $ hasValueFlow=f2.2 SPURIOUS: hasValueFlow=f2.1 +} + +function f3() { + function inner(x) { + return (function() { + return x; // captured variable to return + })(); + } + sink(inner(source("f3.1"))); // $ hasValueFlow=f3.1 SPURIOUS: hasValueFlow=f3.2 + sink(inner(source("f3.2"))); // $ hasValueFlow=f3.2 SPURIOUS: hasValueFlow=f3.1 +} + +function f4() { + function inner(x) { + let y; + (function() { + y = x; // captured variable to captured variable + })(); + return y; + } + sink(inner(source("f4.1"))); // $ hasValueFlow=f4.1 + sink(inner(source("f4.2"))); // $ hasValueFlow=f4.2 +} From 37676f41aab91cfd69623433c86f5f5ec0c85693 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 1 Nov 2024 09:17:35 +0100 Subject: [PATCH 04/19] JS: Remove jump steps from IIFE steps --- .../dataflow/internal/DataFlowPrivate.qll | 17 ++++++++++++++++- .../ql/test/library-tests/TripleDot/iife.js | 12 ++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll index b33f908cc689..864b6f13d7e7 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll @@ -1217,6 +1217,20 @@ predicate simpleLocalFlowStep(Node node1, Node node2) { predicate localMustFlowStep(Node node1, Node node2) { node1 = node2.getImmediatePredecessor() } +/** + * Holds if `node1 -> node2` should be removed as a jump step. + * + * Currently this is done as a workaround for the local steps generated from IIFEs. + */ +private predicate excludedJumpStep(Node node1, Node node2) { + exists(ImmediatelyInvokedFunctionExpr iife | + iife.argumentPassing(node2.asExpr(), node1.asExpr()) + or + node1 = iife.getAReturnedExpr().flow() and + node2 = iife.getInvocation().flow() + ) +} + /** * Holds if data can flow from `node1` to `node2` through a non-local step * that does not follow a call edge. For example, a step through a global @@ -1224,7 +1238,8 @@ predicate localMustFlowStep(Node node1, Node node2) { node1 = node2.getImmediate */ predicate jumpStep(Node node1, Node node2) { valuePreservingStep(node1, node2) and - node1.getContainer() != node2.getContainer() + node1.getContainer() != node2.getContainer() and + not excludedJumpStep(node1, node2) or FlowSummaryPrivate::Steps::summaryJumpStep(node1.(FlowSummaryNode).getSummaryNode(), node2.(FlowSummaryNode).getSummaryNode()) diff --git a/javascript/ql/test/library-tests/TripleDot/iife.js b/javascript/ql/test/library-tests/TripleDot/iife.js index 8da09f67c0ab..2dcf3213c42e 100644 --- a/javascript/ql/test/library-tests/TripleDot/iife.js +++ b/javascript/ql/test/library-tests/TripleDot/iife.js @@ -4,8 +4,8 @@ function f1() { return p; // argument to return })(x); } - sink(inner(source("f1.1"))); // $ hasValueFlow=f1.1 SPURIOUS: hasValueFlow=f1.2 - sink(inner(source("f1.2"))); // $ hasValueFlow=f1.2 SPURIOUS: hasValueFlow=f1.1 + sink(inner(source("f1.1"))); // $ hasValueFlow=f1.1 + sink(inner(source("f1.2"))); // $ hasValueFlow=f1.2 } function f2() { @@ -16,8 +16,8 @@ function f2() { })(x); return y; } - sink(inner(source("f2.1"))); // $ hasValueFlow=f2.1 SPURIOUS: hasValueFlow=f2.2 - sink(inner(source("f2.2"))); // $ hasValueFlow=f2.2 SPURIOUS: hasValueFlow=f2.1 + sink(inner(source("f2.1"))); // $ MISSING: hasValueFlow=f2.1 + sink(inner(source("f2.2"))); // $ MISSING: hasValueFlow=f2.2 } function f3() { @@ -26,8 +26,8 @@ function f3() { return x; // captured variable to return })(); } - sink(inner(source("f3.1"))); // $ hasValueFlow=f3.1 SPURIOUS: hasValueFlow=f3.2 - sink(inner(source("f3.2"))); // $ hasValueFlow=f3.2 SPURIOUS: hasValueFlow=f3.1 + sink(inner(source("f3.1"))); // $ hasValueFlow=f3.1 + sink(inner(source("f3.2"))); // $ hasValueFlow=f3.2 } function f4() { From 023dcce400d58950315fc7858a6166fe9208fc54 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 1 Nov 2024 10:29:53 +0100 Subject: [PATCH 05/19] JS: Disable variable capture heuristic Bailing out can be more expensive as the resulting jump steps themselves cause perf issues. The limit of 100 variables per scope has also been added in the interim, which handles the cases that this needed to cover. --- .../dataflow/internal/VariableCapture.qll | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/VariableCapture.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/VariableCapture.qll index 247f76908940..413bdc1babb2 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/VariableCapture.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/VariableCapture.qll @@ -23,35 +23,11 @@ module VariableCaptureConfig implements InputSig { or isTopLevelLike(container.(js::ImmediatelyInvokedFunctionExpr).getEnclosingContainer()) or - // Functions declared in a top-level with no parameters and can't generate flow-through, except through 'this' - // which we rule out with a few syntactic checks. In this case we treat its captured variables as singletons. - // NOTE: This was done to prevent a blow-up in fiddlesalad where a function called 'Runtime' captures 7381 variables but is only called once. - exists(js::Function fun | - container = fun and - fun.getNumParameter() = 0 and - isTopLevelLike(fun.getEnclosingContainer()) and - not mayHaveFlowThroughThisArgument(fun) - ) - or - // Container declaring >100 captured variables tend to be singletons and are too expensive anyway + // Containers declaring >100 captured variables tend to be singletons and are too expensive anyway strictcount(js::LocalVariable v | v.isCaptured() and v.getDeclaringContainer() = container) > 100 } - private predicate hasLocalConstructorCall(js::Function fun) { - fun = getLambdaFromVariable(any(js::NewExpr e).getCallee().(js::VarAccess).getVariable()) - } - - private predicate mayHaveFlowThroughThisArgument(js::Function fun) { - any(js::ThisExpr e).getBinder() = fun and - not hasLocalConstructorCall(fun) and // 'this' argument is assumed to be a fresh object - ( - exists(fun.getAReturnedExpr()) - or - exists(js::YieldExpr e | e.getContainer() = fun) - ) - } - class CapturedVariable extends LocalVariableOrThis { CapturedVariable() { DataFlowImplCommon::forceCachingInSameStage() and From d2daec4c66d048499eef7f71ec267d986fb07f12 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 19 Nov 2024 13:10:34 +0100 Subject: [PATCH 06/19] JS: Add tests explaining why the IIFE in f2 didn't work --- .../ql/test/library-tests/TripleDot/iife.js | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/javascript/ql/test/library-tests/TripleDot/iife.js b/javascript/ql/test/library-tests/TripleDot/iife.js index 2dcf3213c42e..31899f21b758 100644 --- a/javascript/ql/test/library-tests/TripleDot/iife.js +++ b/javascript/ql/test/library-tests/TripleDot/iife.js @@ -41,3 +41,42 @@ function f4() { sink(inner(source("f4.1"))); // $ hasValueFlow=f4.1 sink(inner(source("f4.2"))); // $ hasValueFlow=f4.2 } + +function f5() { + function inner(x) { + let y; + function nested(p) { + y = p; + } + nested(x); + return y; + } + sink(inner(source("f5.1"))); // $ hasValueFlow=f5.1 + sink(inner(source("f5.2"))); // $ hasValueFlow=f5.2 +} + +function f6() { + function inner(x) { + let y; + function nested(p) { + y = p; + } + (nested)(x); // same as f5, except the callee is parenthesised here + return y; + } + sink(inner(source("f6.1"))); // $ MISSING: hasValueFlow=f6.1 + sink(inner(source("f6.2"))); // $ MISSING: hasValueFlow=f6.2 +} + +function f7() { + function inner(x) { + let y; + let nested = (function (p) { + y = p; + }); + nested(x); // same as f5, except the function definition is parenthesised + return y; + } + sink(inner(source("f7.1"))); // $ MISSING: hasValueFlow=f7.1 + sink(inner(source("f7.2"))); // $ MISSING: hasValueFlow=f7.2 +} From 80a5a5909e99c14bc0c00b010d4a3eb6098d8e03 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 19 Nov 2024 13:21:32 +0100 Subject: [PATCH 07/19] JS: Use getUnderlyingValue() a few places in VariableCapture --- .../javascript/dataflow/internal/VariableCapture.qll | 6 ++++-- javascript/ql/test/library-tests/TripleDot/iife.js | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/VariableCapture.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/VariableCapture.qll index 413bdc1babb2..6e381a792efe 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/VariableCapture.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/VariableCapture.qll @@ -8,7 +8,7 @@ module VariableCaptureConfig implements InputSig { private js::Function getLambdaFromVariable(js::LocalVariable variable) { result.getVariable() = variable or - result = variable.getAnAssignedExpr() + result = variable.getAnAssignedExpr().getUnderlyingValue() or exists(js::ClassDeclStmt cls | result = cls.getConstructor().getBody() and @@ -148,9 +148,11 @@ module VariableCaptureConfig implements InputSig { predicate hasAliasedAccess(Expr e) { e = this or + e.(js::Expr).getUnderlyingValue() = this + or exists(js::LocalVariable variable | this = getLambdaFromVariable(variable) and - e = variable.getAnAccess() + e.(js::Expr).getUnderlyingValue() = variable.getAnAccess() ) } } diff --git a/javascript/ql/test/library-tests/TripleDot/iife.js b/javascript/ql/test/library-tests/TripleDot/iife.js index 31899f21b758..1697ce8d3342 100644 --- a/javascript/ql/test/library-tests/TripleDot/iife.js +++ b/javascript/ql/test/library-tests/TripleDot/iife.js @@ -16,8 +16,8 @@ function f2() { })(x); return y; } - sink(inner(source("f2.1"))); // $ MISSING: hasValueFlow=f2.1 - sink(inner(source("f2.2"))); // $ MISSING: hasValueFlow=f2.2 + sink(inner(source("f2.1"))); // $ hasValueFlow=f2.1 + sink(inner(source("f2.2"))); // $ hasValueFlow=f2.2 } function f3() { @@ -64,8 +64,8 @@ function f6() { (nested)(x); // same as f5, except the callee is parenthesised here return y; } - sink(inner(source("f6.1"))); // $ MISSING: hasValueFlow=f6.1 - sink(inner(source("f6.2"))); // $ MISSING: hasValueFlow=f6.2 + sink(inner(source("f6.1"))); // $ hasValueFlow=f6.1 + sink(inner(source("f6.2"))); // $ hasValueFlow=f6.2 } function f7() { @@ -77,6 +77,6 @@ function f7() { nested(x); // same as f5, except the function definition is parenthesised return y; } - sink(inner(source("f7.1"))); // $ MISSING: hasValueFlow=f7.1 - sink(inner(source("f7.2"))); // $ MISSING: hasValueFlow=f7.2 + sink(inner(source("f7.1"))); // $ hasValueFlow=f7.1 + sink(inner(source("f7.2"))); // $ hasValueFlow=f7.2 } From 01669908f22c308d388beb42cef58dcf1a906c9f Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 1 Nov 2024 10:30:24 +0100 Subject: [PATCH 08/19] JS: Block InsecureRandomness flow into test files --- .../security/dataflow/InsecureRandomnessQuery.qll | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/InsecureRandomnessQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/InsecureRandomnessQuery.qll index b4804e8f4644..bf095a637684 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/InsecureRandomnessQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/InsecureRandomnessQuery.qll @@ -11,6 +11,7 @@ import javascript private import semmle.javascript.security.SensitiveActions import InsecureRandomnessCustomizations::InsecureRandomness private import InsecureRandomnessCustomizations::InsecureRandomness as InsecureRandomness +private import semmle.javascript.filters.ClassifyFiles as ClassifyFiles /** * A taint tracking configuration for random values that are not cryptographically secure. @@ -20,7 +21,11 @@ module InsecureRandomnessConfig 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 + ClassifyFiles::isTestFile(node.getFile()) + } predicate isBarrierOut(DataFlow::Node node) { // stop propagation at the sinks to avoid double reporting From d1c9e47d231834736824a4d615a38a041a37f50e Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 1 Nov 2024 13:18:32 +0100 Subject: [PATCH 09/19] JS: More aggressive test file classification --- javascript/ql/lib/semmle/javascript/filters/ClassifyFiles.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/filters/ClassifyFiles.qll b/javascript/ql/lib/semmle/javascript/filters/ClassifyFiles.qll index 5dd442263516..8d392bc04482 100644 --- a/javascript/ql/lib/semmle/javascript/filters/ClassifyFiles.qll +++ b/javascript/ql/lib/semmle/javascript/filters/ClassifyFiles.qll @@ -61,6 +61,8 @@ predicate isTestFile(File f) { ) or f.getAbsolutePath().regexpMatch(".*/__(mocks|tests)__/.*") + or + f.getBaseName().matches("%.test.%") } /** From b7dd455aff8f930a8df90fde3b8aaea1b795a90e Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 21 Nov 2024 09:21:36 +0100 Subject: [PATCH 10/19] JS: Add test case --- javascript/ql/test/library-tests/TripleDot/exceptions.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/javascript/ql/test/library-tests/TripleDot/exceptions.js b/javascript/ql/test/library-tests/TripleDot/exceptions.js index dddd53a046ec..9d105fb8d564 100644 --- a/javascript/ql/test/library-tests/TripleDot/exceptions.js +++ b/javascript/ql/test/library-tests/TripleDot/exceptions.js @@ -9,8 +9,11 @@ function e1() { array.forEach(x => { throw source('e1.2'); }); + array.forEach(() => { + throw source('e1.3'); // Same as e1.2 but without callback parameters + }); } catch (err) { - sink(err); // $ hasValueFlow=e1.2 MISSING: hasValueFlow=e1.1 + sink(err); // $ hasValueFlow=e1.2 MISSING: hasValueFlow=e1.1 hasValueFlow=e1.3 } } From dcdb2e5133115b0bf4db0ddb954ab721d26cd2f8 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 21 Nov 2024 09:23:38 +0100 Subject: [PATCH 11/19] JS: Fix callback check so it works without parameters --- .../internal/flow_summaries/ExceptionFlow.qll | 13 ++++++++++++- .../ql/test/library-tests/TripleDot/exceptions.js | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll index aee3be8a95b6..04616e05dc63 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll @@ -8,6 +8,17 @@ private import semmle.javascript.dataflow.internal.AdditionalFlowInternal private import semmle.javascript.dataflow.FlowSummary private import semmle.javascript.internal.flow_summaries.Promises +private predicate isCallback(DataFlow::SourceNode node) { + node instanceof DataFlow::FunctionNode + or + node instanceof DataFlow::PartialInvokeNode + or + exists(DataFlow::SourceNode prev | + isCallback(prev) and + DataFlow::argumentPassingStep(_, prev.getALocalUse(), _, node) + ) +} + /** * Summary that propagates exceptions out of callbacks back to the caller. */ @@ -21,7 +32,7 @@ private class ExceptionFlowSummary extends SummarizedCallable { ["then", "catch", "finally", "addEventListener", EventEmitter::on()] and not result = promiseConstructorRef().getAnInvocation() and // Restrict to cases where a callback is known to flow in, as lambda flow in DataFlowImplCommon blows up otherwise - exists(result.getABoundCallbackParameter(_, _)) + isCallback(result.getAnArgument().getALocalSource()) } override predicate propagatesFlowExt(string input, string output, boolean preservesValue) { diff --git a/javascript/ql/test/library-tests/TripleDot/exceptions.js b/javascript/ql/test/library-tests/TripleDot/exceptions.js index 9d105fb8d564..0bd472e120c0 100644 --- a/javascript/ql/test/library-tests/TripleDot/exceptions.js +++ b/javascript/ql/test/library-tests/TripleDot/exceptions.js @@ -13,7 +13,7 @@ function e1() { throw source('e1.3'); // Same as e1.2 but without callback parameters }); } catch (err) { - sink(err); // $ hasValueFlow=e1.2 MISSING: hasValueFlow=e1.1 hasValueFlow=e1.3 + sink(err); // $ hasValueFlow=e1.2 hasValueFlow=e1.3 MISSING: hasValueFlow=e1.1 } } @@ -47,7 +47,7 @@ function e3() { throw source('e3.4'); }); } catch (err) { - sink(err); // $ MISSING: hasValueFlow=e3.4 + sink(err); // $ hasValueFlow=e3.4 } } From 948d21ca071dadb8b639a08fff65b1a586fbd8fa Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 21 Nov 2024 10:23:08 +0100 Subject: [PATCH 12/19] JS: Propagate exceptions from summarized callables by default --- .../dataflow/internal/DataFlowNode.qll | 3 ++ .../dataflow/internal/DataFlowPrivate.qll | 38 +++++++++++++++++++ .../library-tests/TripleDot/exceptions.js | 4 +- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowNode.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowNode.qll index 6d2be5a1537c..26bff4fdf806 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowNode.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowNode.qll @@ -100,6 +100,9 @@ private module Cached { // So it doesn't cause negative recursion but it might look a bit surprising. FlowSummaryPrivate::Steps::summaryStoreStep(sn, MkAwaited(), _) } or + TFlowSummaryDefaultExceptionalReturn(FlowSummaryImpl::Public::SummarizedCallable callable) { + not DataFlowPrivate::mentionsExceptionalReturn(callable) + } or TSynthCaptureNode(VariableCapture::VariableCaptureOutput::SynthesizedCaptureNode node) or TGenericSynthesizedNode(AstNode node, string tag, DataFlowPrivate::DataFlowCallable container) { any(AdditionalFlowInternal flow).needsSynthesizedNode(node, tag, container) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll index 864b6f13d7e7..8c1f74ae5354 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll @@ -112,6 +112,33 @@ class FlowSummaryIntermediateAwaitStoreNode extends DataFlow::Node, } } +predicate mentionsExceptionalReturn(FlowSummaryImpl::Public::SummarizedCallable callable) { + exists(FlowSummaryImpl::Private::SummaryNode node | node.getSummarizedCallable() = callable | + FlowSummaryImpl::Private::summaryReturnNode(node, MkExceptionalReturnKind()) + or + FlowSummaryImpl::Private::summaryOutNode(_, node, MkExceptionalReturnKind()) + ) +} + +/** + * Exceptional return node in a summarized callable whose summary does not mention `ReturnValue[exception]`. + * + * By default, every call inside such a callable will forward their exceptional return to the caller's + * exceptional return, i.e. exceptions are not caught. + */ +class FlowSummaryDefaultExceptionalReturn extends DataFlow::Node, + TFlowSummaryDefaultExceptionalReturn +{ + private FlowSummaryImpl::Public::SummarizedCallable callable; + + FlowSummaryDefaultExceptionalReturn() { this = TFlowSummaryDefaultExceptionalReturn(callable) } + + FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() { result = callable } + + cached + override string toString() { result = "[default exceptional return] " + callable } +} + class CaptureNode extends DataFlow::Node, TSynthCaptureNode { /** Gets the underlying node from the variable-capture library. */ VariableCaptureOutput::SynthesizedCaptureNode getNode() { @@ -296,6 +323,9 @@ private predicate returnNodeImpl(DataFlow::Node node, ReturnKind kind) { ) or FlowSummaryImpl::Private::summaryReturnNode(node.(FlowSummaryNode).getSummaryNode(), kind) + or + node instanceof FlowSummaryDefaultExceptionalReturn and + kind = MkExceptionalReturnKind() } private DataFlow::Node getAnOutNodeImpl(DataFlowCall call, ReturnKind kind) { @@ -311,6 +341,10 @@ private DataFlow::Node getAnOutNodeImpl(DataFlowCall call, ReturnKind kind) { or FlowSummaryImpl::Private::summaryOutNode(call.(SummaryCall).getReceiver(), result.(FlowSummaryNode).getSummaryNode(), kind) + or + kind = MkExceptionalReturnKind() and + result.(FlowSummaryDefaultExceptionalReturn).getSummarizedCallable() = + call.(SummaryCall).getSummarizedCallable() } class ReturnNode extends DataFlow::Node { @@ -505,6 +539,8 @@ DataFlowCallable nodeGetEnclosingCallable(Node node) { or result.asLibraryCallable() = node.(FlowSummaryIntermediateAwaitStoreNode).getSummarizedCallable() or + result.asLibraryCallable() = node.(FlowSummaryDefaultExceptionalReturn).getSummarizedCallable() + or node = TGenericSynthesizedNode(_, _, result) } @@ -865,6 +901,8 @@ class SummaryCall extends DataFlowCall, MkSummaryCall { /** Gets the receiver node. */ FlowSummaryImpl::Private::SummaryNode getReceiver() { result = receiver } + + FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() { result = enclosingCallable } } /** diff --git a/javascript/ql/test/library-tests/TripleDot/exceptions.js b/javascript/ql/test/library-tests/TripleDot/exceptions.js index 0bd472e120c0..8b4262e7a6b9 100644 --- a/javascript/ql/test/library-tests/TripleDot/exceptions.js +++ b/javascript/ql/test/library-tests/TripleDot/exceptions.js @@ -13,7 +13,7 @@ function e1() { throw source('e1.3'); // Same as e1.2 but without callback parameters }); } catch (err) { - sink(err); // $ hasValueFlow=e1.2 hasValueFlow=e1.3 MISSING: hasValueFlow=e1.1 + sink(err); // $ hasValueFlow=e1.2 hasValueFlow=e1.3 hasValueFlow=e1.1 } } @@ -58,7 +58,7 @@ function e4() { try { thrower([source("e4.1")]); } catch (e) { - sink(e); // $ MISSING: hasValueFlow=e4.1 + sink(e); // $ hasValueFlow=e4.1 } try { thrower(["safe"]); From 84820adf3ca1a06b646d9dead9db6eda3354ac26 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 21 Nov 2024 10:57:07 +0100 Subject: [PATCH 13/19] Add test for exception flow out of finally() --- .../ql/test/library-tests/TripleDot/exceptions.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/javascript/ql/test/library-tests/TripleDot/exceptions.js b/javascript/ql/test/library-tests/TripleDot/exceptions.js index 8b4262e7a6b9..241e6aca55df 100644 --- a/javascript/ql/test/library-tests/TripleDot/exceptions.js +++ b/javascript/ql/test/library-tests/TripleDot/exceptions.js @@ -66,3 +66,16 @@ function e4() { sink(e); } } + +async function e5() { + try { + Promise.resolve(0).finally(() => { + throw source("e5.1"); + }); + await Promise.resolve(0).finally(() => { + throw source("e5.2"); + }); + } catch (e) { + sink(e); // $ hasValueFlow=e5.2 + } +} From 4e62a512c53ca11eb3ba09faf63a2c750fcdba3a Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 21 Nov 2024 11:00:49 +0100 Subject: [PATCH 14/19] JS: Only apply exception propagator when no other summary applies Previously a few Promise-related methods were special-cased, which is no longer needed. --- .../dataflow/internal/DataFlowPrivate.qll | 19 ++++++++++++++++++- .../dataflow/internal/FlowSummaryPrivate.qll | 7 ++++++- .../internal/flow_summaries/ExceptionFlow.qll | 9 +++++---- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll index 8c1f74ae5354..d71ea9ff8325 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll @@ -438,6 +438,19 @@ abstract class LibraryCallable extends string { DataFlow::InvokeNode getACallSimple() { none() } } +/** Internal subclass of `LibraryCallable`, whose member predicates should not be visible on `SummarizedCallable`. */ +abstract class LibraryCallableInternal extends LibraryCallable { + bindingset[this] + LibraryCallableInternal() { any() } + + /** + * Gets a call to this library callable. + * + * Same as `getACall()` but is evaluated later and may depend negatively on `getACall()`. + */ + DataFlow::InvokeNode getACallStage2() { none() } +} + private predicate isParameterNodeImpl(Node p, DataFlowCallable c, ParameterPosition pos) { exists(Parameter parameter | parameter = c.asSourceCallable().(Function).getParameter(pos.asPositional()) and @@ -1014,7 +1027,11 @@ DataFlowCallable viableCallable(DataFlowCall node) { or exists(LibraryCallable callable | result = MkLibraryCallable(callable) and - node.asOrdinaryCall() = [callable.getACall(), callable.getACallSimple()] + node.asOrdinaryCall() = + [ + callable.getACall(), callable.getACallSimple(), + callable.(LibraryCallableInternal).getACallStage2() + ] ) or result.asSourceCallableNotExterns() = node.asImpliedLambdaCall() diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/FlowSummaryPrivate.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/FlowSummaryPrivate.qll index 6ae42a90bd2b..460a2be4f1d4 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/FlowSummaryPrivate.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/FlowSummaryPrivate.qll @@ -9,6 +9,7 @@ private import sharedlib.DataFlowImplCommon private import sharedlib.FlowSummaryImpl::Private as Private private import sharedlib.FlowSummaryImpl::Public private import codeql.dataflow.internal.AccessPathSyntax as AccessPathSyntax +private import semmle.javascript.internal.flow_summaries.ExceptionFlow /** * A class of callables that are candidates for flow summary modeling. @@ -131,7 +132,11 @@ ReturnKind getStandardReturnValueKind() { result = MkNormalReturnKind() and Stag private module FlowSummaryStepInput implements Private::StepsInputSig { DataFlowCall getACall(SummarizedCallable sc) { exists(LibraryCallable callable | callable = sc | - result.asOrdinaryCall() = [callable.getACall(), callable.getACallSimple()] + result.asOrdinaryCall() = + [ + callable.getACall(), callable.getACallSimple(), + callable.(LibraryCallableInternal).getACallStage2() + ] ) } } diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll index 04616e05dc63..a1d82cbf6086 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll @@ -5,6 +5,7 @@ private import javascript private import FlowSummaryUtil private import semmle.javascript.dataflow.internal.AdditionalFlowInternal +private import semmle.javascript.dataflow.internal.DataFlowPrivate private import semmle.javascript.dataflow.FlowSummary private import semmle.javascript.internal.flow_summaries.Promises @@ -22,14 +23,14 @@ private predicate isCallback(DataFlow::SourceNode node) { /** * Summary that propagates exceptions out of callbacks back to the caller. */ -private class ExceptionFlowSummary extends SummarizedCallable { +private class ExceptionFlowSummary extends SummarizedCallable, LibraryCallableInternal { ExceptionFlowSummary() { this = "Exception propagator" } - override DataFlow::CallNode getACall() { + override DataFlow::CallNode getACallStage2() { not exists(result.getACallee()) and + not exists(SummarizedCallable c | result = [c.getACall(), c.getACallSimple()]) and // Avoid a few common cases where the exception should not propagate back - not result.getCalleeName() = - ["then", "catch", "finally", "addEventListener", EventEmitter::on()] and + not result.getCalleeName() = ["addEventListener", EventEmitter::on()] and not result = promiseConstructorRef().getAnInvocation() and // Restrict to cases where a callback is known to flow in, as lambda flow in DataFlowImplCommon blows up otherwise isCallback(result.getAnArgument().getALocalSource()) From ce00bd2cc969a8532c3c7e851c84346b75ef8c20 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 21 Nov 2024 11:06:43 +0100 Subject: [PATCH 15/19] JS: More docs --- .../javascript/internal/flow_summaries/ExceptionFlow.qll | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll index a1d82cbf6086..c81dadadfb64 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/ExceptionFlow.qll @@ -22,6 +22,10 @@ private predicate isCallback(DataFlow::SourceNode node) { /** * Summary that propagates exceptions out of callbacks back to the caller. + * + * This summary only applies to calls that have no other call targets. + * See also `FlowSummaryDefaultExceptionalReturn`, which handles calls that have a summary target, + * but where the summary does not mention `ReturnValue[exception]`. */ private class ExceptionFlowSummary extends SummarizedCallable, LibraryCallableInternal { ExceptionFlowSummary() { this = "Exception propagator" } From 9dad2d62d76c40015376b585a09b9209c2b1358c Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 21 Nov 2024 12:54:11 +0100 Subject: [PATCH 16/19] JS: Update DataFlowConsistency --- .../javascript/dataflow/internal/DataFlowImplConsistency.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowImplConsistency.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowImplConsistency.qll index aa892c810dbe..e5ce83d86c85 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowImplConsistency.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowImplConsistency.qll @@ -26,6 +26,8 @@ private module ConsistencyConfig implements InputSig { or n instanceof FlowSummaryDynamicParameterArrayNode or + n instanceof FlowSummaryDefaultExceptionalReturn + or n instanceof GenericSynthesizedNode or n = DataFlow::globalAccessPathRootPseudoNode() From 1ac7591fafe990c47f78f6f080b9a5291cab4d1e Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 21 Nov 2024 12:57:34 +0100 Subject: [PATCH 17/19] JS: Update missed flow in capture-flow.js We previously caught this flow because of a heuristic in capture flow. We'll have to fix it properly later. --- .../library-tests/TaintTracking/BasicTaintTracking.expected | 2 +- .../test/library-tests/TaintTracking/DataFlowTracking.expected | 2 +- javascript/ql/test/library-tests/TaintTracking/capture-flow.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected index 2d3c12597b9e..069da0c2c6fb 100644 --- a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected @@ -17,6 +17,7 @@ legacyDataFlowDifference | callbacks.js:44:17:44:24 | source() | callbacks.js:38:35:38:35 | x | only flow with NEW data flow library | | capture-flow.js:89:13:89:20 | source() | capture-flow.js:89:6:89:21 | test3c(source()) | only flow with NEW data flow library | | capture-flow.js:101:12:101:19 | source() | capture-flow.js:102:6:102:20 | test5("safe")() | only flow with OLD data flow library | +| capture-flow.js:126:25:126:32 | source() | capture-flow.js:123:14:123:26 | orderingTaint | only flow with OLD data flow library | | constructor-calls.js:4:18:4:25 | source() | constructor-calls.js:40:8:40:14 | e.taint | only flow with NEW data flow library | | constructor-calls.js:4:18:4:25 | source() | constructor-calls.js:44:8:44:19 | f_safe.taint | only flow with NEW data flow library | | constructor-calls.js:20:15:20:22 | source() | constructor-calls.js:39:8:39:14 | e.param | only flow with NEW data flow library | @@ -109,7 +110,6 @@ flow | capture-flow.js:101:12:101:19 | source() | capture-flow.js:101:6:101:22 | test5(source())() | | capture-flow.js:110:12:110:19 | source() | capture-flow.js:106:14:106:14 | x | | capture-flow.js:118:37:118:44 | source() | capture-flow.js:114:14:114:14 | x | -| capture-flow.js:126:25:126:32 | source() | capture-flow.js:123:14:123:26 | orderingTaint | | capture-flow.js:126:25:126:32 | source() | capture-flow.js:129:14:129:26 | orderingTaint | | capture-flow.js:177:26:177:33 | source() | capture-flow.js:173:14:173:14 | x | | capture-flow.js:187:34:187:41 | source() | capture-flow.js:183:14:183:14 | x | diff --git a/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected b/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected index 7e083f535501..1f4f69f0274b 100644 --- a/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected @@ -11,6 +11,7 @@ legacyDataFlowDifference | callbacks.js:44:17:44:24 | source() | callbacks.js:38:35:38:35 | x | only flow with NEW data flow library | | capture-flow.js:89:13:89:20 | source() | capture-flow.js:89:6:89:21 | test3c(source()) | only flow with NEW data flow library | | capture-flow.js:101:12:101:19 | source() | capture-flow.js:102:6:102:20 | test5("safe")() | only flow with OLD data flow library | +| capture-flow.js:126:25:126:32 | source() | capture-flow.js:123:14:123:26 | orderingTaint | only flow with OLD data flow library | | constructor-calls.js:4:18:4:25 | source() | constructor-calls.js:40:8:40:14 | e.taint | only flow with NEW data flow library | | constructor-calls.js:4:18:4:25 | source() | constructor-calls.js:44:8:44:19 | f_safe.taint | only flow with NEW data flow library | | constructor-calls.js:20:15:20:22 | source() | constructor-calls.js:39:8:39:14 | e.param | only flow with NEW data flow library | @@ -84,7 +85,6 @@ flow | capture-flow.js:101:12:101:19 | source() | capture-flow.js:101:6:101:22 | test5(source())() | | capture-flow.js:110:12:110:19 | source() | capture-flow.js:106:14:106:14 | x | | capture-flow.js:118:37:118:44 | source() | capture-flow.js:114:14:114:14 | x | -| capture-flow.js:126:25:126:32 | source() | capture-flow.js:123:14:123:26 | orderingTaint | | capture-flow.js:126:25:126:32 | source() | capture-flow.js:129:14:129:26 | orderingTaint | | capture-flow.js:177:26:177:33 | source() | capture-flow.js:173:14:173:14 | x | | capture-flow.js:187:34:187:41 | source() | capture-flow.js:183:14:183:14 | x | diff --git a/javascript/ql/test/library-tests/TaintTracking/capture-flow.js b/javascript/ql/test/library-tests/TaintTracking/capture-flow.js index 20e654da6d08..ba792b889bf4 100644 --- a/javascript/ql/test/library-tests/TaintTracking/capture-flow.js +++ b/javascript/ql/test/library-tests/TaintTracking/capture-flow.js @@ -120,7 +120,7 @@ global.doEscape(testEscapeViaReturn(source())); function ordering() { var orderingTaint; global.addEventListener('click', () => { - sink(orderingTaint); // NOT OK + sink(orderingTaint); // NOT OK [INCONSISTENCY] }); global.addEventListener('load', () => { orderingTaint = source(); From 7a7743202486615e8bbaca44d2de5e9d75b543ca Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 21 Nov 2024 13:33:10 +0100 Subject: [PATCH 18/19] JS: Update lost result in insecure-download The VariableCapture library consumes one component of the access path limit, which means we lose this result --- .../CWE-829/InsecureDownload.expected | 28 ------------------- .../Security/CWE-829/insecure-download.js | 4 +-- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/javascript/ql/test/query-tests/Security/CWE-829/InsecureDownload.expected b/javascript/ql/test/query-tests/Security/CWE-829/InsecureDownload.expected index d697f55bdd79..aa8290bac3bf 100644 --- a/javascript/ql/test/query-tests/Security/CWE-829/InsecureDownload.expected +++ b/javascript/ql/test/query-tests/Security/CWE-829/InsecureDownload.expected @@ -1,18 +1,4 @@ nodes -| insecure-download.js:4:28:4:36 | installer [url] | semmle.label | installer [url] | -| insecure-download.js:5:16:5:24 | installer [url] | semmle.label | installer [url] | -| insecure-download.js:5:16:5:28 | installer.url | semmle.label | installer.url | -| insecure-download.js:7:9:11:5 | constants [buildTools, installerUrl] | semmle.label | constants [buildTools, installerUrl] | -| insecure-download.js:7:21:11:5 | {\\n ... }\\n } [buildTools, installerUrl] | semmle.label | {\\n ... }\\n } [buildTools, installerUrl] | -| insecure-download.js:8:21:10:9 | {\\n ... } [installerUrl] | semmle.label | {\\n ... } [installerUrl] | -| insecure-download.js:9:27:9:138 | 'http:/ ... ll.exe' | semmle.label | 'http:/ ... ll.exe' | -| insecure-download.js:13:15:13:47 | buildTools [installerUrl] | semmle.label | buildTools [installerUrl] | -| insecure-download.js:13:28:13:36 | constants [buildTools, installerUrl] | semmle.label | constants [buildTools, installerUrl] | -| insecure-download.js:13:28:13:47 | constants.buildTools [installerUrl] | semmle.label | constants.buildTools [installerUrl] | -| insecure-download.js:14:16:16:9 | {\\n ... } [url] | semmle.label | {\\n ... } [url] | -| insecure-download.js:15:18:15:27 | buildTools [installerUrl] | semmle.label | buildTools [installerUrl] | -| insecure-download.js:15:18:15:40 | buildTo ... llerUrl | semmle.label | buildTo ... llerUrl | -| insecure-download.js:19:19:19:46 | getBuil ... rPath() [url] | semmle.label | getBuil ... rPath() [url] | | insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" | semmle.label | "http:/ ... fe.APK" | | insecure-download.js:36:9:36:45 | url | semmle.label | url | | insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" | semmle.label | "http:/ ... fe.APK" | @@ -22,25 +8,11 @@ nodes | insecure-download.js:48:12:48:38 | "http:/ ... unsafe" | semmle.label | "http:/ ... unsafe" | | insecure-download.js:52:11:52:45 | "http:/ ... nknown" | semmle.label | "http:/ ... nknown" | edges -| insecure-download.js:4:28:4:36 | installer [url] | insecure-download.js:5:16:5:24 | installer [url] | provenance | | -| insecure-download.js:5:16:5:24 | installer [url] | insecure-download.js:5:16:5:28 | installer.url | provenance | | -| insecure-download.js:7:9:11:5 | constants [buildTools, installerUrl] | insecure-download.js:13:28:13:36 | constants [buildTools, installerUrl] | provenance | | -| insecure-download.js:7:21:11:5 | {\\n ... }\\n } [buildTools, installerUrl] | insecure-download.js:7:9:11:5 | constants [buildTools, installerUrl] | provenance | | -| insecure-download.js:8:21:10:9 | {\\n ... } [installerUrl] | insecure-download.js:7:21:11:5 | {\\n ... }\\n } [buildTools, installerUrl] | provenance | | -| insecure-download.js:9:27:9:138 | 'http:/ ... ll.exe' | insecure-download.js:8:21:10:9 | {\\n ... } [installerUrl] | provenance | | -| insecure-download.js:13:15:13:47 | buildTools [installerUrl] | insecure-download.js:15:18:15:27 | buildTools [installerUrl] | provenance | | -| insecure-download.js:13:28:13:36 | constants [buildTools, installerUrl] | insecure-download.js:13:28:13:47 | constants.buildTools [installerUrl] | provenance | | -| insecure-download.js:13:28:13:47 | constants.buildTools [installerUrl] | insecure-download.js:13:15:13:47 | buildTools [installerUrl] | provenance | | -| insecure-download.js:14:16:16:9 | {\\n ... } [url] | insecure-download.js:19:19:19:46 | getBuil ... rPath() [url] | provenance | | -| insecure-download.js:15:18:15:27 | buildTools [installerUrl] | insecure-download.js:15:18:15:40 | buildTo ... llerUrl | provenance | | -| insecure-download.js:15:18:15:40 | buildTo ... llerUrl | insecure-download.js:14:16:16:9 | {\\n ... } [url] | provenance | | -| insecure-download.js:19:19:19:46 | getBuil ... rPath() [url] | insecure-download.js:4:28:4:36 | installer [url] | provenance | | | insecure-download.js:36:9:36:45 | url | insecure-download.js:37:23:37:25 | url | provenance | | | insecure-download.js:36:9:36:45 | url | insecure-download.js:39:26:39:28 | url | provenance | | | insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" | insecure-download.js:36:9:36:45 | url | provenance | | subpaths #select -| insecure-download.js:5:16:5:28 | installer.url | insecure-download.js:9:27:9:138 | 'http:/ ... ll.exe' | insecure-download.js:5:16:5:28 | installer.url | $@ of sensitive file from $@. | insecure-download.js:5:9:5:44 | nugget( ... => { }) | Download | insecure-download.js:9:27:9:138 | 'http:/ ... ll.exe' | HTTP source | | insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" | insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" | insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" | $@ of sensitive file from $@. | insecure-download.js:30:5:30:43 | nugget( ... e.APK") | Download | insecure-download.js:30:12:30:42 | "http:/ ... fe.APK" | HTTP source | | insecure-download.js:37:23:37:25 | url | insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" | insecure-download.js:37:23:37:25 | url | $@ of sensitive file from $@. | insecure-download.js:37:5:37:42 | cp.exec ... () {}) | Download | insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" | HTTP source | | insecure-download.js:39:26:39:28 | url | insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" | insecure-download.js:39:26:39:28 | url | $@ of sensitive file from $@. | insecure-download.js:39:5:39:46 | cp.exec ... () {}) | Download | insecure-download.js:36:15:36:45 | "http:/ ... fe.APK" | HTTP source | diff --git a/javascript/ql/test/query-tests/Security/CWE-829/insecure-download.js b/javascript/ql/test/query-tests/Security/CWE-829/insecure-download.js index 4f662e8e1ddb..9a62286218df 100644 --- a/javascript/ql/test/query-tests/Security/CWE-829/insecure-download.js +++ b/javascript/ql/test/query-tests/Security/CWE-829/insecure-download.js @@ -2,7 +2,7 @@ const nugget = require('nugget'); function foo() { function downloadTools(installer) { - nugget(installer.url, {}, () => { }) // NOT OK + nugget(installer.url, {}, () => { }) // NOT OK [INCONSISTENCY] - access path too deep } var constants = { buildTools: { @@ -56,4 +56,4 @@ function test() { $.get("http://example.org/unsafe.unknown", function( data ) { writeFileAtomic('foo.safe', data, {}, function (err) {}); // OK }); -} \ No newline at end of file +} From 930a7b6e282be86bb7073e16a6f9af1fe4f7f6f5 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 21 Nov 2024 13:33:39 +0100 Subject: [PATCH 19/19] JS: Update output changes to nodes/edges/subpaths --- .../Security/CWE-079/DomBasedXss/Xss.expected | 45 +++++++++++++++---- .../XssWithAdditionalSources.expected | 45 +++++++++++++++---- 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/Xss.expected b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/Xss.expected index 25651d7e0607..868e9c0eeddc 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/Xss.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/Xss.expected @@ -153,6 +153,9 @@ nodes | express.js:7:15:7:33 | req.param("wobble") | semmle.label | req.param("wobble") | | jquery.js:2:7:2:40 | tainted | semmle.label | tainted | | jquery.js:2:17:2:40 | documen ... .search | semmle.label | documen ... .search | +| jquery.js:4:5:4:11 | tainted | semmle.label | tainted | +| jquery.js:5:13:5:19 | tainted | semmle.label | tainted | +| jquery.js:6:11:6:17 | tainted | semmle.label | tainted | | jquery.js:7:5:7:34 | "
" | semmle.label | "
" | | jquery.js:7:20:7:26 | tainted | semmle.label | tainted | | jquery.js:8:18:8:34 | "XSS: " + tainted | semmle.label | "XSS: " + tainted | @@ -321,6 +324,9 @@ nodes | tooltip.jsx:6:20:6:30 | window.name | semmle.label | window.name | | tooltip.jsx:10:25:10:30 | source | semmle.label | source | | tooltip.jsx:11:25:11:30 | source | semmle.label | source | +| tooltip.jsx:17:11:17:33 | provide [source] | semmle.label | provide [source] | +| tooltip.jsx:17:21:17:33 | props.provide [source] | semmle.label | props.provide [source] | +| tooltip.jsx:18:51:18:57 | provide [source] | semmle.label | provide [source] | | tooltip.jsx:18:51:18:59 | provide() | semmle.label | provide() | | tooltip.jsx:22:11:22:30 | source | semmle.label | source | | tooltip.jsx:22:20:22:30 | window.name | semmle.label | window.name | @@ -491,6 +497,7 @@ nodes | tst.js:355:10:355:42 | target | semmle.label | target | | tst.js:355:19:355:42 | documen ... .search | semmle.label | documen ... .search | | tst.js:356:16:356:21 | target | semmle.label | target | +| tst.js:357:20:357:25 | target | semmle.label | target | | tst.js:360:21:360:26 | target | semmle.label | target | | tst.js:363:18:363:23 | target | semmle.label | target | | tst.js:371:7:371:39 | target | semmle.label | target | @@ -725,13 +732,20 @@ edges | dragAndDrop.ts:71:27:71:61 | e.dataT ... /html') | dragAndDrop.ts:71:13:71:61 | droppedHtml | provenance | | | event-handler-receiver.js:2:49:2:61 | location.href | event-handler-receiver.js:2:31:2:83 | '

' | provenance | | | event-handler-receiver.js:2:49:2:61 | location.href | event-handler-receiver.js:2:31:2:83 | '

' | provenance | Config | +| jquery.js:2:7:2:40 | tainted | jquery.js:4:5:4:11 | tainted | provenance | | +| jquery.js:2:7:2:40 | tainted | jquery.js:5:13:5:19 | tainted | provenance | | +| jquery.js:2:7:2:40 | tainted | jquery.js:6:11:6:17 | tainted | provenance | | | jquery.js:2:7:2:40 | tainted | jquery.js:7:20:7:26 | tainted | provenance | | | jquery.js:2:7:2:40 | tainted | jquery.js:8:28:8:34 | tainted | provenance | | | jquery.js:2:7:2:40 | tainted | jquery.js:36:25:36:31 | tainted | provenance | | -| jquery.js:2:7:2:40 | tainted | jquery.js:37:31:37:37 | tainted | provenance | | | jquery.js:2:17:2:40 | documen ... .search | jquery.js:2:7:2:40 | tainted | provenance | | +| jquery.js:4:5:4:11 | tainted | jquery.js:5:13:5:19 | tainted | provenance | | +| jquery.js:5:13:5:19 | tainted | jquery.js:6:11:6:17 | tainted | provenance | | +| jquery.js:6:11:6:17 | tainted | jquery.js:7:20:7:26 | tainted | provenance | | | jquery.js:7:20:7:26 | tainted | jquery.js:7:5:7:34 | "
" | provenance | Config | +| jquery.js:7:20:7:26 | tainted | jquery.js:8:28:8:34 | tainted | provenance | | | jquery.js:8:28:8:34 | tainted | jquery.js:8:18:8:34 | "XSS: " + tainted | provenance | | +| jquery.js:8:28:8:34 | tainted | jquery.js:36:25:36:31 | tainted | provenance | | | jquery.js:10:13:10:20 | location | jquery.js:10:13:10:31 | location.toString() | provenance | | | jquery.js:10:13:10:31 | location.toString() | jquery.js:10:5:10:40 | "" + ... "" | provenance | Config | | jquery.js:14:38:14:57 | window.location.hash | jquery.js:14:19:14:58 | decodeU ... n.hash) | provenance | | @@ -752,6 +766,7 @@ edges | jquery.js:27:5:27:8 | hash | jquery.js:27:5:27:25 | hash.re ... #', '') | provenance | Config | | jquery.js:28:5:28:26 | window. ... .search | jquery.js:28:5:28:43 | window. ... ?', '') | provenance | Config | | jquery.js:34:13:34:16 | hash | jquery.js:34:5:34:25 | '' + ... '' | provenance | Config | +| jquery.js:36:25:36:31 | tainted | jquery.js:37:31:37:37 | tainted | provenance | | | jquery.js:37:31:37:37 | tainted | jquery.js:37:25:37:37 | () => tainted | provenance | Config | | json-stringify.jsx:5:9:5:36 | locale | json-stringify.jsx:11:51:11:56 | locale | provenance | | | json-stringify.jsx:5:9:5:36 | locale | json-stringify.jsx:19:56:19:61 | locale | provenance | | @@ -863,9 +878,12 @@ edges | tooltip.jsx:6:11:6:30 | source | tooltip.jsx:10:25:10:30 | source | provenance | | | tooltip.jsx:6:11:6:30 | source | tooltip.jsx:11:25:11:30 | source | provenance | | | tooltip.jsx:6:20:6:30 | window.name | tooltip.jsx:6:11:6:30 | source | provenance | | -| tooltip.jsx:22:11:22:30 | source | tooltip.jsx:23:38:23:43 | source | provenance | | +| tooltip.jsx:17:11:17:33 | provide [source] | tooltip.jsx:18:51:18:57 | provide [source] | provenance | | +| tooltip.jsx:17:21:17:33 | props.provide [source] | tooltip.jsx:17:11:17:33 | provide [source] | provenance | | +| tooltip.jsx:18:51:18:57 | provide [source] | tooltip.jsx:18:51:18:59 | provide() | provenance | | +| tooltip.jsx:18:51:18:57 | provide [source] | tooltip.jsx:23:38:23:43 | source | provenance | | +| tooltip.jsx:22:11:22:30 | source | tooltip.jsx:17:21:17:33 | props.provide [source] | provenance | | | tooltip.jsx:22:20:22:30 | window.name | tooltip.jsx:22:11:22:30 | source | provenance | | -| tooltip.jsx:23:38:23:43 | source | tooltip.jsx:18:51:18:59 | provide() | provenance | | | translate.js:6:7:6:39 | target | translate.js:7:42:7:47 | target | provenance | | | translate.js:6:16:6:39 | documen ... .search | translate.js:6:7:6:39 | target | provenance | | | translate.js:7:7:7:61 | searchParams | translate.js:9:27:9:38 | searchParams | provenance | | @@ -964,24 +982,30 @@ edges | tst.js:184:19:184:42 | documen ... .search | tst.js:184:9:184:42 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:199:67:199:73 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:200:67:200:73 | tainted | provenance | | -| tst.js:197:9:197:42 | tainted | tst.js:204:35:204:41 | tainted | provenance | | -| tst.js:197:9:197:42 | tainted | tst.js:206:46:206:52 | tainted | provenance | | -| tst.js:197:9:197:42 | tainted | tst.js:207:38:207:44 | tainted | provenance | | -| tst.js:197:9:197:42 | tainted | tst.js:208:35:208:41 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:236:35:236:41 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:238:20:238:26 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:240:23:240:29 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:241:23:241:29 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:255:23:255:29 | tainted | provenance | | | tst.js:197:19:197:42 | documen ... .search | tst.js:197:9:197:42 | tainted | provenance | | +| tst.js:199:67:199:73 | tainted | tst.js:200:67:200:73 | tainted | provenance | | +| tst.js:200:67:200:73 | tainted | tst.js:204:35:204:41 | tainted | provenance | | +| tst.js:200:67:200:73 | tainted | tst.js:206:46:206:52 | tainted | provenance | | +| tst.js:200:67:200:73 | tainted | tst.js:207:38:207:44 | tainted | provenance | | +| tst.js:200:67:200:73 | tainted | tst.js:208:35:208:41 | tainted | provenance | | +| tst.js:200:67:200:73 | tainted | tst.js:236:35:236:41 | tainted | provenance | | | tst.js:204:35:204:41 | tainted | tst.js:212:28:212:46 | this.state.tainted1 | provenance | | | tst.js:206:46:206:52 | tainted | tst.js:213:28:213:46 | this.state.tainted2 | provenance | | | tst.js:207:38:207:44 | tainted | tst.js:214:28:214:46 | this.state.tainted3 | provenance | | | tst.js:208:35:208:41 | tainted | tst.js:218:32:218:49 | prevState.tainted4 | provenance | | | tst.js:236:35:236:41 | tainted | tst.js:225:28:225:46 | this.props.tainted1 | provenance | | +| tst.js:236:35:236:41 | tainted | tst.js:238:20:238:26 | tainted | provenance | | | tst.js:238:20:238:26 | tainted | tst.js:226:28:226:46 | this.props.tainted2 | provenance | | +| tst.js:238:20:238:26 | tainted | tst.js:240:23:240:29 | tainted | provenance | | | tst.js:240:23:240:29 | tainted | tst.js:227:28:227:46 | this.props.tainted3 | provenance | | +| tst.js:240:23:240:29 | tainted | tst.js:241:23:241:29 | tainted | provenance | | | tst.js:241:23:241:29 | tainted | tst.js:231:32:231:49 | prevProps.tainted4 | provenance | | +| tst.js:241:23:241:29 | tainted | tst.js:255:23:255:29 | tainted | provenance | | | tst.js:247:39:247:55 | props.propTainted | tst.js:251:60:251:82 | this.st ... Tainted | provenance | | | tst.js:255:23:255:29 | tainted | tst.js:247:39:247:55 | props.propTainted | provenance | | | tst.js:285:9:285:29 | tainted | tst.js:288:59:288:65 | tainted | provenance | | @@ -1003,9 +1027,11 @@ edges | tst.js:348:7:348:39 | target | tst.js:349:12:349:17 | target | provenance | | | tst.js:348:16:348:39 | documen ... .search | tst.js:348:7:348:39 | target | provenance | | | tst.js:355:10:355:42 | target | tst.js:356:16:356:21 | target | provenance | | -| tst.js:355:10:355:42 | target | tst.js:360:21:360:26 | target | provenance | | -| tst.js:355:10:355:42 | target | tst.js:363:18:363:23 | target | provenance | | +| tst.js:355:10:355:42 | target | tst.js:357:20:357:25 | target | provenance | | | tst.js:355:19:355:42 | documen ... .search | tst.js:355:10:355:42 | target | provenance | | +| tst.js:356:16:356:21 | target | tst.js:357:20:357:25 | target | provenance | | +| tst.js:357:20:357:25 | target | tst.js:360:21:360:26 | target | provenance | | +| tst.js:357:20:357:25 | target | tst.js:363:18:363:23 | target | provenance | | | tst.js:371:7:371:39 | target | tst.js:374:18:374:23 | target | provenance | | | tst.js:371:16:371:39 | documen ... .search | tst.js:371:7:371:39 | target | provenance | | | tst.js:381:7:381:39 | target | tst.js:384:18:384:23 | target | provenance | | @@ -1116,6 +1142,7 @@ subpaths | optionalSanitizer.js:34:28:34:35 | tainted2 | optionalSanitizer.js:28:24:28:24 | x | optionalSanitizer.js:29:12:29:12 | x | optionalSanitizer.js:34:16:34:36 | sanitiz ... inted2) | | optionalSanitizer.js:41:28:41:35 | tainted3 | optionalSanitizer.js:28:24:28:24 | x | optionalSanitizer.js:29:12:29:12 | x | optionalSanitizer.js:41:16:41:36 | sanitiz ... inted3) | | optionalSanitizer.js:45:41:45:46 | target | optionalSanitizer.js:28:24:28:24 | x | optionalSanitizer.js:29:12:29:12 | x | optionalSanitizer.js:45:29:45:47 | sanitizeBad(target) | +| tooltip.jsx:18:51:18:57 | provide [source] | tooltip.jsx:23:38:23:43 | source | tooltip.jsx:23:38:23:43 | source | tooltip.jsx:18:51:18:59 | provide() | | tst.js:40:20:40:43 | documen ... .search | tst.js:36:14:36:14 | x | tst.js:37:10:37:10 | x | tst.js:40:16:40:44 | baz(doc ... search) | | tst.js:46:21:46:44 | documen ... .search | tst.js:42:15:42:15 | s | tst.js:43:10:43:31 | "
" ...
" | tst.js:46:16:46:45 | wrap(do ... search) | | tst.js:54:21:54:44 | documen ... .search | tst.js:48:15:48:15 | s | tst.js:50:12:50:22 | s.substr(1) | tst.js:54:16:54:45 | chop(do ... search) | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected index a3c256a1b721..cef53b2b3f5f 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/DomBasedXss/XssWithAdditionalSources.expected @@ -153,6 +153,9 @@ nodes | express.js:7:15:7:33 | req.param("wobble") | semmle.label | req.param("wobble") | | jquery.js:2:7:2:40 | tainted | semmle.label | tainted | | jquery.js:2:17:2:40 | documen ... .search | semmle.label | documen ... .search | +| jquery.js:4:5:4:11 | tainted | semmle.label | tainted | +| jquery.js:5:13:5:19 | tainted | semmle.label | tainted | +| jquery.js:6:11:6:17 | tainted | semmle.label | tainted | | jquery.js:7:5:7:34 | "
" | semmle.label | "
" | | jquery.js:7:20:7:26 | tainted | semmle.label | tainted | | jquery.js:8:18:8:34 | "XSS: " + tainted | semmle.label | "XSS: " + tainted | @@ -326,6 +329,9 @@ nodes | tooltip.jsx:6:20:6:30 | window.name | semmle.label | window.name | | tooltip.jsx:10:25:10:30 | source | semmle.label | source | | tooltip.jsx:11:25:11:30 | source | semmle.label | source | +| tooltip.jsx:17:11:17:33 | provide [source] | semmle.label | provide [source] | +| tooltip.jsx:17:21:17:33 | props.provide [source] | semmle.label | props.provide [source] | +| tooltip.jsx:18:51:18:57 | provide [source] | semmle.label | provide [source] | | tooltip.jsx:18:51:18:59 | provide() | semmle.label | provide() | | tooltip.jsx:22:11:22:30 | source | semmle.label | source | | tooltip.jsx:22:20:22:30 | window.name | semmle.label | window.name | @@ -496,6 +502,7 @@ nodes | tst.js:355:10:355:42 | target | semmle.label | target | | tst.js:355:19:355:42 | documen ... .search | semmle.label | documen ... .search | | tst.js:356:16:356:21 | target | semmle.label | target | +| tst.js:357:20:357:25 | target | semmle.label | target | | tst.js:360:21:360:26 | target | semmle.label | target | | tst.js:363:18:363:23 | target | semmle.label | target | | tst.js:371:7:371:39 | target | semmle.label | target | @@ -745,13 +752,20 @@ edges | dragAndDrop.ts:71:27:71:61 | e.dataT ... /html') | dragAndDrop.ts:71:13:71:61 | droppedHtml | provenance | | | event-handler-receiver.js:2:49:2:61 | location.href | event-handler-receiver.js:2:31:2:83 | '

' | provenance | | | event-handler-receiver.js:2:49:2:61 | location.href | event-handler-receiver.js:2:31:2:83 | '

' | provenance | Config | +| jquery.js:2:7:2:40 | tainted | jquery.js:4:5:4:11 | tainted | provenance | | +| jquery.js:2:7:2:40 | tainted | jquery.js:5:13:5:19 | tainted | provenance | | +| jquery.js:2:7:2:40 | tainted | jquery.js:6:11:6:17 | tainted | provenance | | | jquery.js:2:7:2:40 | tainted | jquery.js:7:20:7:26 | tainted | provenance | | | jquery.js:2:7:2:40 | tainted | jquery.js:8:28:8:34 | tainted | provenance | | | jquery.js:2:7:2:40 | tainted | jquery.js:36:25:36:31 | tainted | provenance | | -| jquery.js:2:7:2:40 | tainted | jquery.js:37:31:37:37 | tainted | provenance | | | jquery.js:2:17:2:40 | documen ... .search | jquery.js:2:7:2:40 | tainted | provenance | | +| jquery.js:4:5:4:11 | tainted | jquery.js:5:13:5:19 | tainted | provenance | | +| jquery.js:5:13:5:19 | tainted | jquery.js:6:11:6:17 | tainted | provenance | | +| jquery.js:6:11:6:17 | tainted | jquery.js:7:20:7:26 | tainted | provenance | | | jquery.js:7:20:7:26 | tainted | jquery.js:7:5:7:34 | "
" | provenance | Config | +| jquery.js:7:20:7:26 | tainted | jquery.js:8:28:8:34 | tainted | provenance | | | jquery.js:8:28:8:34 | tainted | jquery.js:8:18:8:34 | "XSS: " + tainted | provenance | | +| jquery.js:8:28:8:34 | tainted | jquery.js:36:25:36:31 | tainted | provenance | | | jquery.js:10:13:10:20 | location | jquery.js:10:13:10:31 | location.toString() | provenance | | | jquery.js:10:13:10:31 | location.toString() | jquery.js:10:5:10:40 | "" + ... "" | provenance | Config | | jquery.js:14:38:14:57 | window.location.hash | jquery.js:14:19:14:58 | decodeU ... n.hash) | provenance | | @@ -772,6 +786,7 @@ edges | jquery.js:27:5:27:8 | hash | jquery.js:27:5:27:25 | hash.re ... #', '') | provenance | Config | | jquery.js:28:5:28:26 | window. ... .search | jquery.js:28:5:28:43 | window. ... ?', '') | provenance | Config | | jquery.js:34:13:34:16 | hash | jquery.js:34:5:34:25 | '' + ... '' | provenance | Config | +| jquery.js:36:25:36:31 | tainted | jquery.js:37:31:37:37 | tainted | provenance | | | jquery.js:37:31:37:37 | tainted | jquery.js:37:25:37:37 | () => tainted | provenance | Config | | json-stringify.jsx:5:9:5:36 | locale | json-stringify.jsx:11:51:11:56 | locale | provenance | | | json-stringify.jsx:5:9:5:36 | locale | json-stringify.jsx:19:56:19:61 | locale | provenance | | @@ -887,9 +902,12 @@ edges | tooltip.jsx:6:11:6:30 | source | tooltip.jsx:10:25:10:30 | source | provenance | | | tooltip.jsx:6:11:6:30 | source | tooltip.jsx:11:25:11:30 | source | provenance | | | tooltip.jsx:6:20:6:30 | window.name | tooltip.jsx:6:11:6:30 | source | provenance | | -| tooltip.jsx:22:11:22:30 | source | tooltip.jsx:23:38:23:43 | source | provenance | | +| tooltip.jsx:17:11:17:33 | provide [source] | tooltip.jsx:18:51:18:57 | provide [source] | provenance | | +| tooltip.jsx:17:21:17:33 | props.provide [source] | tooltip.jsx:17:11:17:33 | provide [source] | provenance | | +| tooltip.jsx:18:51:18:57 | provide [source] | tooltip.jsx:18:51:18:59 | provide() | provenance | | +| tooltip.jsx:18:51:18:57 | provide [source] | tooltip.jsx:23:38:23:43 | source | provenance | | +| tooltip.jsx:22:11:22:30 | source | tooltip.jsx:17:21:17:33 | props.provide [source] | provenance | | | tooltip.jsx:22:20:22:30 | window.name | tooltip.jsx:22:11:22:30 | source | provenance | | -| tooltip.jsx:23:38:23:43 | source | tooltip.jsx:18:51:18:59 | provide() | provenance | | | translate.js:6:7:6:39 | target | translate.js:7:42:7:47 | target | provenance | | | translate.js:6:16:6:39 | documen ... .search | translate.js:6:7:6:39 | target | provenance | | | translate.js:7:7:7:61 | searchParams | translate.js:9:27:9:38 | searchParams | provenance | | @@ -988,24 +1006,30 @@ edges | tst.js:184:19:184:42 | documen ... .search | tst.js:184:9:184:42 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:199:67:199:73 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:200:67:200:73 | tainted | provenance | | -| tst.js:197:9:197:42 | tainted | tst.js:204:35:204:41 | tainted | provenance | | -| tst.js:197:9:197:42 | tainted | tst.js:206:46:206:52 | tainted | provenance | | -| tst.js:197:9:197:42 | tainted | tst.js:207:38:207:44 | tainted | provenance | | -| tst.js:197:9:197:42 | tainted | tst.js:208:35:208:41 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:236:35:236:41 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:238:20:238:26 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:240:23:240:29 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:241:23:241:29 | tainted | provenance | | | tst.js:197:9:197:42 | tainted | tst.js:255:23:255:29 | tainted | provenance | | | tst.js:197:19:197:42 | documen ... .search | tst.js:197:9:197:42 | tainted | provenance | | +| tst.js:199:67:199:73 | tainted | tst.js:200:67:200:73 | tainted | provenance | | +| tst.js:200:67:200:73 | tainted | tst.js:204:35:204:41 | tainted | provenance | | +| tst.js:200:67:200:73 | tainted | tst.js:206:46:206:52 | tainted | provenance | | +| tst.js:200:67:200:73 | tainted | tst.js:207:38:207:44 | tainted | provenance | | +| tst.js:200:67:200:73 | tainted | tst.js:208:35:208:41 | tainted | provenance | | +| tst.js:200:67:200:73 | tainted | tst.js:236:35:236:41 | tainted | provenance | | | tst.js:204:35:204:41 | tainted | tst.js:212:28:212:46 | this.state.tainted1 | provenance | | | tst.js:206:46:206:52 | tainted | tst.js:213:28:213:46 | this.state.tainted2 | provenance | | | tst.js:207:38:207:44 | tainted | tst.js:214:28:214:46 | this.state.tainted3 | provenance | | | tst.js:208:35:208:41 | tainted | tst.js:218:32:218:49 | prevState.tainted4 | provenance | | | tst.js:236:35:236:41 | tainted | tst.js:225:28:225:46 | this.props.tainted1 | provenance | | +| tst.js:236:35:236:41 | tainted | tst.js:238:20:238:26 | tainted | provenance | | | tst.js:238:20:238:26 | tainted | tst.js:226:28:226:46 | this.props.tainted2 | provenance | | +| tst.js:238:20:238:26 | tainted | tst.js:240:23:240:29 | tainted | provenance | | | tst.js:240:23:240:29 | tainted | tst.js:227:28:227:46 | this.props.tainted3 | provenance | | +| tst.js:240:23:240:29 | tainted | tst.js:241:23:241:29 | tainted | provenance | | | tst.js:241:23:241:29 | tainted | tst.js:231:32:231:49 | prevProps.tainted4 | provenance | | +| tst.js:241:23:241:29 | tainted | tst.js:255:23:255:29 | tainted | provenance | | | tst.js:247:39:247:55 | props.propTainted | tst.js:251:60:251:82 | this.st ... Tainted | provenance | | | tst.js:255:23:255:29 | tainted | tst.js:247:39:247:55 | props.propTainted | provenance | | | tst.js:285:9:285:29 | tainted | tst.js:288:59:288:65 | tainted | provenance | | @@ -1027,9 +1051,11 @@ edges | tst.js:348:7:348:39 | target | tst.js:349:12:349:17 | target | provenance | | | tst.js:348:16:348:39 | documen ... .search | tst.js:348:7:348:39 | target | provenance | | | tst.js:355:10:355:42 | target | tst.js:356:16:356:21 | target | provenance | | -| tst.js:355:10:355:42 | target | tst.js:360:21:360:26 | target | provenance | | -| tst.js:355:10:355:42 | target | tst.js:363:18:363:23 | target | provenance | | +| tst.js:355:10:355:42 | target | tst.js:357:20:357:25 | target | provenance | | | tst.js:355:19:355:42 | documen ... .search | tst.js:355:10:355:42 | target | provenance | | +| tst.js:356:16:356:21 | target | tst.js:357:20:357:25 | target | provenance | | +| tst.js:357:20:357:25 | target | tst.js:360:21:360:26 | target | provenance | | +| tst.js:357:20:357:25 | target | tst.js:363:18:363:23 | target | provenance | | | tst.js:371:7:371:39 | target | tst.js:374:18:374:23 | target | provenance | | | tst.js:371:16:371:39 | documen ... .search | tst.js:371:7:371:39 | target | provenance | | | tst.js:381:7:381:39 | target | tst.js:384:18:384:23 | target | provenance | | @@ -1152,6 +1178,7 @@ subpaths | optionalSanitizer.js:34:28:34:35 | tainted2 | optionalSanitizer.js:28:24:28:24 | x | optionalSanitizer.js:29:12:29:12 | x | optionalSanitizer.js:34:16:34:36 | sanitiz ... inted2) | | optionalSanitizer.js:41:28:41:35 | tainted3 | optionalSanitizer.js:28:24:28:24 | x | optionalSanitizer.js:29:12:29:12 | x | optionalSanitizer.js:41:16:41:36 | sanitiz ... inted3) | | optionalSanitizer.js:45:41:45:46 | target | optionalSanitizer.js:28:24:28:24 | x | optionalSanitizer.js:29:12:29:12 | x | optionalSanitizer.js:45:29:45:47 | sanitizeBad(target) | +| tooltip.jsx:18:51:18:57 | provide [source] | tooltip.jsx:23:38:23:43 | source | tooltip.jsx:23:38:23:43 | source | tooltip.jsx:18:51:18:59 | provide() | | tst.js:40:20:40:43 | documen ... .search | tst.js:36:14:36:14 | x | tst.js:37:10:37:10 | x | tst.js:40:16:40:44 | baz(doc ... search) | | tst.js:46:21:46:44 | documen ... .search | tst.js:42:15:42:15 | s | tst.js:43:10:43:31 | "
" ...
" | tst.js:46:16:46:45 | wrap(do ... search) | | tst.js:54:21:54:44 | documen ... .search | tst.js:48:15:48:15 | s | tst.js:50:12:50:22 | s.substr(1) | tst.js:54:16:54:45 | chop(do ... search) |