From 17a6d54e4dbd80dcc828240ff1d798c5f1dcce85 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 19 Aug 2024 10:29:04 +0200 Subject: [PATCH 01/21] JS: Setup basic support for threat-models Integration with RemoteFlowSource is not straightforward, so postponing that for later Naming in other languages: - `SourceNode` (for QL only modeling) - `ThreatModelFlowSource` (for active sources from QL or data-extensions) However, since we use `LocalSourceNode` in Python, and `SourceNode` in JS (for local source nodes), it seems a bit confusing to follow the same naming convention as other languages, and instead I came up with new names. --- javascript/ql/lib/qlpack.yml | 1 + .../ql/lib/semmle/javascript/Concepts.qll | 48 +++++++++++++++++++ .../frameworks/data/ModelsAsData.qll | 13 +++++ 3 files changed, 62 insertions(+) diff --git a/javascript/ql/lib/qlpack.yml b/javascript/ql/lib/qlpack.yml index 34347c17efd5..49862735c744 100644 --- a/javascript/ql/lib/qlpack.yml +++ b/javascript/ql/lib/qlpack.yml @@ -9,6 +9,7 @@ dependencies: codeql/dataflow: ${workspace} codeql/mad: ${workspace} codeql/regex: ${workspace} + codeql/threat-models: ${workspace} codeql/tutorial: ${workspace} codeql/util: ${workspace} codeql/xml: ${workspace} diff --git a/javascript/ql/lib/semmle/javascript/Concepts.qll b/javascript/ql/lib/semmle/javascript/Concepts.qll index 14102556a874..2bb0799f2262 100644 --- a/javascript/ql/lib/semmle/javascript/Concepts.qll +++ b/javascript/ql/lib/semmle/javascript/Concepts.qll @@ -5,6 +5,54 @@ */ import javascript +private import codeql.threatmodels.ThreatModels + +/** + * A data flow source, for a specific threat-model. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `ThreatModelSource::Range` instead. + */ +class ThreatModelSource extends DataFlow::Node instanceof ThreatModelSource::Range { + /** + * Gets a string that represents the source kind with respect to threat modeling. + */ + string getThreatModel() { result = super.getThreatModel() } + + /** Gets a string that describes the type of this threat-model source. */ + string getSourceType() { result = super.getSourceType() } +} + +/** Provides a class for modeling new sources for specific threat-models. */ +module ThreatModelSource { + /** + * A data flow source, for a specific threat-model. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `ThreatModelSource` instead. + */ + abstract class Range extends DataFlow::Node { + /** + * Gets a string that represents the source kind with respect to threat modeling. + */ + abstract string getThreatModel(); + + /** Gets a string that describes the type of this threat-model source. */ + abstract string getSourceType(); + } +} + +/** + * A data flow source that is enabled in the current threat model configuration. + */ +class ActiveThreatModelSource extends DataFlow::Node { + ActiveThreatModelSource() { + exists(string kind | + currentThreatModel(kind) and + this.(ThreatModelSource).getThreatModel() = kind + ) + } +} /** * A data flow node that executes an operating system command, diff --git a/javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsData.qll b/javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsData.qll index 6e95955749b4..856a61276a0a 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsData.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/data/ModelsAsData.qll @@ -32,6 +32,19 @@ private class RemoteFlowSourceFromMaD extends RemoteFlowSource { override string getSourceType() { result = "Remote flow" } } +/** + * A threat-model flow source originating from a data extension. + */ +private class ThreatModelSourceFromDataExtension extends ThreatModelSource::Range { + ThreatModelSourceFromDataExtension() { this = ModelOutput::getASourceNode(_).asSource() } + + override string getThreatModel() { this = ModelOutput::getASourceNode(result).asSource() } + + override string getSourceType() { + result = "Source node (" + this.getThreatModel() + ") [from data-extension]" + } +} + /** * Like `ModelOutput::summaryStep` but with API nodes mapped to data-flow nodes. */ From 05dce8a0be3f099422052b8ee4e415f1c1df4b28 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 19 Aug 2024 10:45:48 +0200 Subject: [PATCH 02/21] JS: Add test showing default active threat-models --- .../threat-models/default/ActiveKinds.expected | 4 ++++ .../library-tests/threat-models/default/ActiveKinds.ql | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 javascript/ql/test/library-tests/threat-models/default/ActiveKinds.expected create mode 100644 javascript/ql/test/library-tests/threat-models/default/ActiveKinds.ql diff --git a/javascript/ql/test/library-tests/threat-models/default/ActiveKinds.expected b/javascript/ql/test/library-tests/threat-models/default/ActiveKinds.expected new file mode 100644 index 000000000000..c471a7cc9129 --- /dev/null +++ b/javascript/ql/test/library-tests/threat-models/default/ActiveKinds.expected @@ -0,0 +1,4 @@ +| default | +| remote | +| request | +| response | diff --git a/javascript/ql/test/library-tests/threat-models/default/ActiveKinds.ql b/javascript/ql/test/library-tests/threat-models/default/ActiveKinds.ql new file mode 100644 index 000000000000..93a1354b7af8 --- /dev/null +++ b/javascript/ql/test/library-tests/threat-models/default/ActiveKinds.ql @@ -0,0 +1,7 @@ +private import codeql.threatmodels.ThreatModels + +from string kind +where + knownThreatModel(kind) and + currentThreatModel(kind) +select kind From dbfbd2c00a3f43b90354fc7b4e88b709b83a4162 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 19 Aug 2024 10:47:42 +0200 Subject: [PATCH 03/21] JS: Remove 'response' from default threat-models I didn't want to put the configuration file in `semmle/javascript/frameworks/**/*.model.yml`, so created `ext/` as in other languages --- .../ql/lib/ext/default-threat-models-fixup.model.yml | 8 ++++++++ javascript/ql/lib/qlpack.yml | 1 + .../threat-models/default/ActiveKinds.expected | 1 - 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 javascript/ql/lib/ext/default-threat-models-fixup.model.yml diff --git a/javascript/ql/lib/ext/default-threat-models-fixup.model.yml b/javascript/ql/lib/ext/default-threat-models-fixup.model.yml new file mode 100644 index 000000000000..31363571544b --- /dev/null +++ b/javascript/ql/lib/ext/default-threat-models-fixup.model.yml @@ -0,0 +1,8 @@ +extensions: + - addsTo: + pack: codeql/threat-models + extensible: threatModelConfiguration + data: + # Since responses are enabled by default in the shared threat-models configuration, + # we need to disable it here to keep existing behavior for the javascript analysis. + - ["response", false, -2147483647] diff --git a/javascript/ql/lib/qlpack.yml b/javascript/ql/lib/qlpack.yml index 49862735c744..2db06b7939e1 100644 --- a/javascript/ql/lib/qlpack.yml +++ b/javascript/ql/lib/qlpack.yml @@ -18,4 +18,5 @@ dataExtensions: - semmle/javascript/frameworks/**/model.yml - semmle/javascript/frameworks/**/*.model.yml - semmle/javascript/security/domains/**/*.model.yml + - ext/*.model.yml warnOnImplicitThis: true diff --git a/javascript/ql/test/library-tests/threat-models/default/ActiveKinds.expected b/javascript/ql/test/library-tests/threat-models/default/ActiveKinds.expected index c471a7cc9129..892f0fa5f6c3 100644 --- a/javascript/ql/test/library-tests/threat-models/default/ActiveKinds.expected +++ b/javascript/ql/test/library-tests/threat-models/default/ActiveKinds.expected @@ -1,4 +1,3 @@ | default | | remote | | request | -| response | From 4b1c0273596be4a30e537cbf4dca3f9c808ec677 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 19 Aug 2024 11:02:44 +0200 Subject: [PATCH 04/21] JS: Integrate RemoteFlowSource with ThreatModelSource --- .../javascript/security/dataflow/RemoteFlowSources.qll | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll index ebd4711288cb..aad00b2d22e5 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll @@ -11,10 +11,9 @@ cached private module Cached { /** A data flow source of remote user input. */ cached - abstract class RemoteFlowSource extends DataFlow::Node { - /** Gets a human-readable string that describes the type of this remote flow source. */ + abstract class RemoteFlowSource extends ThreatModelSource::Range { cached - abstract string getSourceType(); + override string getThreatModel() { result = "remote" } /** * Holds if this can be a user-controlled object, such as a JSON object parsed from user-controlled data. From f733ac19a9dc1c8df5c4672695a84006101f2969 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 19 Aug 2024 11:12:14 +0200 Subject: [PATCH 05/21] JS: Make (most) queries use ActiveThreatModelSource 7 cases looks something like this: ``` class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { RemoteFlowSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } } ``` (some have variations like `not this.(ClientSideRemoteFlowSource).getKind().isPathOrUrl()`) javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll javascript/ql/lib/semmle/javascript/security/dataflow/CorsMisconfigurationForCredentialsCustomizations.qll javascript/ql/lib/semmle/javascript/security/dataflow/RegExpInjectionCustomizations.qll javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll javascript/ql/lib/semmle/javascript/security/dataflow/ResourceExhaustionCustomizations.qll javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll --- .../security/dataflow/CodeInjectionCustomizations.qll | 11 +++++++++-- .../dataflow/ConditionalBypassCustomizations.qll | 10 +++++++--- .../DeepObjectResourceExhaustionCustomizations.qll | 3 ++- .../security/dataflow/DomBasedXssCustomizations.qll | 11 +++++++++-- .../dataflow/NosqlInjectionCustomizations.qll | 11 +++++++++-- .../RemotePropertyInjectionCustomizations.qll | 10 +++++++--- .../security/dataflow/SqlInjectionCustomizations.qll | 11 +++++++++-- .../TemplateObjectInjectionCustomizations.qll | 3 ++- .../dataflow/UnsafeDeserializationCustomizations.qll | 11 +++++++++-- .../UnsafeDynamicMethodAccessCustomizations.qll | 9 +++++++-- .../UnvalidatedDynamicMethodCallCustomizations.qll | 9 +++++++-- .../security/dataflow/XmlBombCustomizations.qll | 11 +++++++++-- .../security/dataflow/XxeCustomizations.qll | 11 +++++++++-- 13 files changed, 95 insertions(+), 26 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll index 4d014768325f..8fded55bc896 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/CodeInjectionCustomizations.qll @@ -27,8 +27,15 @@ module CodeInjection { */ abstract class Sanitizer extends DataFlow::Node { } - /** A source of remote user input, considered as a flow source for code injection. */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { } + /** + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } /** * An expression which may be interpreted as an AngularJS expression. diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ConditionalBypassCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ConditionalBypassCustomizations.qll index 224615c1e8f6..034699cee0d5 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ConditionalBypassCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ConditionalBypassCustomizations.qll @@ -29,10 +29,14 @@ module ConditionalBypass { abstract class Sanitizer extends DataFlow::Node { } /** - * A source of remote user input, considered as a flow source for bypass of - * sensitive action guards. + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { } + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } /** * Holds if `bb` dominates the basic block in which `action` occurs. diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/DeepObjectResourceExhaustionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/DeepObjectResourceExhaustionCustomizations.qll index baa62720717d..58d8d02808ef 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/DeepObjectResourceExhaustionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/DeepObjectResourceExhaustionCustomizations.qll @@ -23,7 +23,8 @@ module DeepObjectResourceExhaustion { override DataFlow::FlowLabel getAFlowLabel() { result = TaintedObject::label() } } - private class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { + /** An active threat-model source, considered as a flow source. */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { override DataFlow::FlowLabel getAFlowLabel() { result.isTaint() } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll index b3ab20583ef8..72d9ae4e55a6 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll @@ -331,8 +331,15 @@ module DomBasedXss { isOptionallySanitizedEdgeInternal(_, node) } - /** A source of remote user input, considered as a flow source for DOM-based XSS. */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { } + /** + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } /** * A flow-label representing tainted values where the prefix is attacker controlled. diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/NosqlInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/NosqlInjectionCustomizations.qll index 988cb59a6e78..536276d5c1dc 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/NosqlInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/NosqlInjectionCustomizations.qll @@ -30,8 +30,15 @@ module NosqlInjection { */ abstract class Sanitizer extends DataFlow::Node { } - /** A source of remote user input, considered as a flow source for NoSql injection. */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { } + /** + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } /** An expression interpreted as a NoSql query, viewed as a sink. */ class NosqlQuerySink extends Sink instanceof NoSql::Query { } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RemotePropertyInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RemotePropertyInjectionCustomizations.qll index 8923946f8310..6157671e6a91 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RemotePropertyInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RemotePropertyInjectionCustomizations.qll @@ -31,10 +31,14 @@ module RemotePropertyInjection { abstract class Sanitizer extends DataFlow::Node { } /** - * A source of remote user input, considered as a flow source for remote property - * injection. + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { } + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } /** * A sink for property writes with dynamically computed property name. diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionCustomizations.qll index 3081a1a80b26..8afb65519ad4 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/SqlInjectionCustomizations.qll @@ -22,8 +22,15 @@ module SqlInjection { */ abstract class Sanitizer extends DataFlow::Node { } - /** A source of remote user input, considered as a flow source for string based query injection. */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { } + /** + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } /** An SQL expression passed to an API call that executes SQL. */ class SqlInjectionExprSink extends Sink instanceof SQL::SqlString { } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/TemplateObjectInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/TemplateObjectInjectionCustomizations.qll index 499115853671..5e7ae35dd88f 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/TemplateObjectInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/TemplateObjectInjectionCustomizations.qll @@ -34,7 +34,8 @@ module TemplateObjectInjection { override DataFlow::FlowLabel getAFlowLabel() { result = TaintedObject::label() } } - private class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { + /** An active threat-model source, considered as a flow source. */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { override DataFlow::FlowLabel getAFlowLabel() { result.isTaint() } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDeserializationCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDeserializationCustomizations.qll index 6871ac93b8e8..2e13e0ee7f9b 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDeserializationCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDeserializationCustomizations.qll @@ -22,8 +22,15 @@ module UnsafeDeserialization { */ abstract class Sanitizer extends DataFlow::Node { } - /** A source of remote user input, considered as a flow source for unsafe deserialization. */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { } + /** + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } private API::Node unsafeYamlSchema() { result = API::moduleImport("js-yaml").getMember("DEFAULT_FULL_SCHEMA") // from older versions diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDynamicMethodAccessCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDynamicMethodAccessCustomizations.qll index ec365b7d4b25..3c5cc713e6eb 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDynamicMethodAccessCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeDynamicMethodAccessCustomizations.qll @@ -52,9 +52,14 @@ module UnsafeDynamicMethodAccess { } /** - * A source of remote user input, considered as a source for unsafe dynamic method access. + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { } + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } /** * A function invocation of an unsafe function, as a sink for remote unsafe dynamic method access. diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCallCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCallCustomizations.qll index d81227bcd68b..73b9d9fc52d8 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCallCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCallCustomizations.qll @@ -71,9 +71,14 @@ module UnvalidatedDynamicMethodCall { } /** - * A source of remote user input, considered as a source for unvalidated dynamic method calls. + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { } + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } /** * The page URL considered as a flow source for unvalidated dynamic method calls. diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/XmlBombCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/XmlBombCustomizations.qll index 9e031fb19fb7..15ba3e2a17d2 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/XmlBombCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/XmlBombCustomizations.qll @@ -23,8 +23,15 @@ module XmlBomb { */ abstract class Sanitizer extends DataFlow::Node { } - /** A source of remote user input, considered as a flow source for XML bomb vulnerabilities. */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { } + /** + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } /** * An access to `document.location`, considered as a flow source for XML bomb vulnerabilities. diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/XxeCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/XxeCustomizations.qll index 9a225e8f2e4e..9f6fe305bdf9 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/XxeCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/XxeCustomizations.qll @@ -23,8 +23,15 @@ module Xxe { */ abstract class Sanitizer extends DataFlow::Node { } - /** A source of remote user input, considered as a flow source for XXE vulnerabilities. */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { } + /** + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } /** * An access to `document.location`, considered as a flow source for XXE vulnerabilities. From 412e841d6929c7a4cf6508a01e721db01df7ac49 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 19 Aug 2024 11:52:00 +0200 Subject: [PATCH 06/21] JS: Add `environment` threat-model source --- .../javascript/frameworks/NodeJSLib.qll | 9 +++++ .../sources/TestSources.expected | 2 + .../threat-models/sources/TestSources.ql | 38 +++++++++++++++++++ .../threat-models/sources/sources.js | 4 ++ 4 files changed, 53 insertions(+) create mode 100644 javascript/ql/test/library-tests/threat-models/sources/TestSources.expected create mode 100644 javascript/ql/test/library-tests/threat-models/sources/TestSources.ql create mode 100644 javascript/ql/test/library-tests/threat-models/sources/sources.js diff --git a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll index 98bb0f615b67..3427591bc1b7 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll @@ -1244,4 +1244,13 @@ module NodeJSLib { result = moduleImport().getAPropertyRead(member) } } + + /** A read of `process.env`, considered as a threat-model source. */ + private class ProcessEnvThreatSource extends ThreatModelSource::Range { + ProcessEnvThreatSource() { this = NodeJSLib::process().getAPropertyRead("env") } + + override string getThreatModel() { result = "environment" } + + override string getSourceType() { result = "process.env" } + } } diff --git a/javascript/ql/test/library-tests/threat-models/sources/TestSources.expected b/javascript/ql/test/library-tests/threat-models/sources/TestSources.expected new file mode 100644 index 000000000000..8ec8033d086e --- /dev/null +++ b/javascript/ql/test/library-tests/threat-models/sources/TestSources.expected @@ -0,0 +1,2 @@ +testFailures +failures diff --git a/javascript/ql/test/library-tests/threat-models/sources/TestSources.ql b/javascript/ql/test/library-tests/threat-models/sources/TestSources.ql new file mode 100644 index 000000000000..3dc112c487ec --- /dev/null +++ b/javascript/ql/test/library-tests/threat-models/sources/TestSources.ql @@ -0,0 +1,38 @@ +import javascript +import testUtilities.InlineExpectationsTest + +class TestSourcesConfiguration extends TaintTracking::Configuration { + TestSourcesConfiguration() { this = "TestSources" } + + override predicate isSource(DataFlow::Node source) { source instanceof ThreatModelSource } + + override predicate isSink(DataFlow::Node sink) { + exists(CallExpr call | + call.getAnArgument() = sink.asExpr() and + call.getCalleeName() = "SINK" + ) + } +} + +private module InlineTestSources implements TestSig { + string getARelevantTag() { result in ["hasFlow", "threat-source"] } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(DataFlow::Node sink | + any(TestSourcesConfiguration c).hasFlow(_, sink) and + value = "" and + location = sink.getLocation() and + tag = "hasFlow" and + element = sink.toString() + ) + or + exists(ThreatModelSource source | + value = source.getThreatModel() and + location = source.getLocation() and + tag = "threat-source" and + element = source.toString() + ) + } +} + +import MakeTest diff --git a/javascript/ql/test/library-tests/threat-models/sources/sources.js b/javascript/ql/test/library-tests/threat-models/sources/sources.js new file mode 100644 index 000000000000..0194af87f6ec --- /dev/null +++ b/javascript/ql/test/library-tests/threat-models/sources/sources.js @@ -0,0 +1,4 @@ +import 'dummy'; + +var x = process.env['foo']; // $ threat-source=environment +SINK(x); // $ hasFlow From 3448751b4cef97f4b59857ea30cc29ac8191bc49 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 19 Aug 2024 14:41:21 +0200 Subject: [PATCH 07/21] JS: Consolidate command-line argument modeling Such that we can reuse the existing modeling, but have it globally applied as a threat-model as well. I Basically just moved the modeling. One important aspect is that this changes is that the previously query-specific `argsParseStep` is now a globally applied taint-step. This seems reasonable, if someone applied the argument parsing to any user-controlled string, it seems correct to propagate that taint for _any_ query. --- javascript/ql/lib/javascript.qll | 1 + .../frameworks/CommandLineArguments.qll | 142 ++++++++++++++++++ ...IndirectCommandInjectionCustomizations.qll | 121 +-------------- .../IndirectCommandInjectionQuery.qll | 4 - .../threat-models/sources/sources.js | 40 +++++ 5 files changed, 186 insertions(+), 122 deletions(-) create mode 100644 javascript/ql/lib/semmle/javascript/frameworks/CommandLineArguments.qll diff --git a/javascript/ql/lib/javascript.qll b/javascript/ql/lib/javascript.qll index 07fb759bd655..7bb2b7676105 100644 --- a/javascript/ql/lib/javascript.qll +++ b/javascript/ql/lib/javascript.qll @@ -81,6 +81,7 @@ import semmle.javascript.frameworks.Classnames import semmle.javascript.frameworks.ClassValidator import semmle.javascript.frameworks.ClientRequests import semmle.javascript.frameworks.ClosureLibrary +import semmle.javascript.frameworks.CommandLineArguments import semmle.javascript.frameworks.CookieLibraries import semmle.javascript.frameworks.Credentials import semmle.javascript.frameworks.CryptoLibraries diff --git a/javascript/ql/lib/semmle/javascript/frameworks/CommandLineArguments.qll b/javascript/ql/lib/semmle/javascript/frameworks/CommandLineArguments.qll new file mode 100644 index 000000000000..db1444db7415 --- /dev/null +++ b/javascript/ql/lib/semmle/javascript/frameworks/CommandLineArguments.qll @@ -0,0 +1,142 @@ +/** Provides modeling for parsed command line arguments. */ + +import javascript + +/** + * An object containing command-line arguments, potentially parsed by a library. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `CommandLineArguments::Range` instead. + */ +class CommandLineArguments extends ThreatModelSource instanceof CommandLineArguments::Range { } + +/** Provides a class for modeling new sources of remote user input. */ +module CommandLineArguments { + /** + * An object containing command-line arguments, potentially parsed by a library. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `CommandLineArguments` instead. + */ + abstract class Range extends ThreatModelSource::Range { + override string getThreatModel() { result = "commandargs" } + + override string getSourceType() { result = "CommandLineArguments" } + } +} + +/** A read of `process.argv`, considered as a threat-model source. */ +private class ProcessArgv extends CommandLineArguments::Range { + ProcessArgv() { + // `process.argv[0]` and `process.argv[1]` are paths to `node` and `main`, and + // therefore should not be considered a threat-source... However, we don't have an + // easy way to exclude them, so we need to allow them. + this = NodeJSLib::process().getAPropertyRead("argv") + } + + override string getSourceType() { result = "process.argv" } +} + +private class DefaultModels extends CommandLineArguments::Range { + DefaultModels() { + // `require('get-them-args')(...)` => `{ unknown: [], a: ... b: ... }` + this = DataFlow::moduleImport("get-them-args").getACall() + or + // `require('optimist').argv` => `{ _: [], a: ... b: ... }` + this = DataFlow::moduleMember("optimist", "argv") + or + // `require("arg")({...spec})` => `{_: [], a: ..., b: ...}` + this = DataFlow::moduleImport("arg").getACall() + or + // `(new (require(argparse)).ArgumentParser({...spec})).parse_args()` => `{a: ..., b: ...}` + this = + API::moduleImport("argparse") + .getMember("ArgumentParser") + .getInstance() + .getMember("parse_args") + .getACall() + or + // `require('command-line-args')({...spec})` => `{a: ..., b: ...}` + this = DataFlow::moduleImport("command-line-args").getACall() + or + // `require('meow')(help, {...spec})` => `{a: ..., b: ....}` + this = DataFlow::moduleImport("meow").getACall() + or + // `require("dashdash").createParser(...spec)` => `{a: ..., b: ...}` + this = + [ + API::moduleImport("dashdash"), + API::moduleImport("dashdash").getMember("createParser").getReturn() + ].getMember("parse").getACall() + or + // `require('commander').myCmdArgumentName` + this = commander().getAMember().asSource() + or + // `require('commander').opt()` => `{a: ..., b: ...}` + this = commander().getMember("opts").getACall() + } +} + +/** + * A step for propagating taint through command line parsing, + * such as `var succ = require("minimist")(pred)`. + */ +private class ArgsParseStep extends TaintTracking::SharedTaintStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists(DataFlow::CallNode call | + call = DataFlow::moduleMember("args", "parse").getACall() or + call = DataFlow::moduleImport(["yargs-parser", "minimist", "subarg"]).getACall() + | + succ = call and + pred = call.getArgument(0) + ) + } +} + +/** + * Gets a Command instance from the `commander` library. + */ +private API::Node commander() { + result = API::moduleImport("commander") + or + // `require("commander").program === require("commander")` + result = commander().getMember("program") + or + result = commander().getMember("Command").getInstance() + or + // lots of chainable methods + result = commander().getAMember().getReturn() +} + +/** + * Gets an instance of `yargs`. + * Either directly imported as a module, or through some chained method call. + */ +private DataFlow::SourceNode yargs() { + result = DataFlow::moduleImport("yargs") + or + // script used to generate list of chained methods: https://gist.github.com/erik-krogh/f8afe952c0577f4b563a993e613269ba + exists(string method | + not method = + // the methods that does not return a chained `yargs` object. + [ + "getContext", "getDemandedOptions", "getDemandedCommands", "getDeprecatedOptions", + "_getParseContext", "getOptions", "getGroups", "getStrict", "getStrictCommands", + "getExitProcess", "locale", "getUsageInstance", "getCommandInstance" + ] + | + result = yargs().getAMethodCall(method) + ) +} + +/** + * An array of command line arguments (`argv`) parsed by the `yargs` library. + */ +private class YargsArgv extends CommandLineArguments::Range { + YargsArgv() { + this = yargs().getAPropertyRead("argv") + or + this = yargs().getAMethodCall("parse") and + this.(DataFlow::MethodCallNode).getNumArgument() = 0 + } +} diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll index b2b94fcca8d3..9dd6ab4b4a91 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll @@ -25,21 +25,6 @@ module IndirectCommandInjection { */ abstract class Sanitizer extends DataFlow::Node { } - /** - * A source of user input from the command-line, considered as a flow source for command injection. - */ - private class CommandLineArgumentsArrayAsSource extends Source instanceof CommandLineArgumentsArray - { } - - /** - * An array of command-line arguments. - */ - class CommandLineArgumentsArray extends DataFlow::SourceNode { - CommandLineArgumentsArray() { - this = DataFlow::globalVarRef("process").getAPropertyRead("argv") - } - } - /** * A read of `process.env`, considered as a flow source for command injection. */ @@ -82,109 +67,9 @@ module IndirectCommandInjection { } /** - * An object containing parsed command-line arguments, considered as a flow source for command injection. + * An object containing command-line arguments, considered as a flow source for command injection. */ - class ParsedCommandLineArgumentsAsSource extends Source { - ParsedCommandLineArgumentsAsSource() { - // `require('get-them-args')(...)` => `{ unknown: [], a: ... b: ... }` - this = DataFlow::moduleImport("get-them-args").getACall() - or - // `require('optimist').argv` => `{ _: [], a: ... b: ... }` - this = DataFlow::moduleMember("optimist", "argv") - or - // `require("arg")({...spec})` => `{_: [], a: ..., b: ...}` - this = DataFlow::moduleImport("arg").getACall() - or - // `(new (require(argparse)).ArgumentParser({...spec})).parse_args()` => `{a: ..., b: ...}` - this = - API::moduleImport("argparse") - .getMember("ArgumentParser") - .getInstance() - .getMember("parse_args") - .getACall() - or - // `require('command-line-args')({...spec})` => `{a: ..., b: ...}` - this = DataFlow::moduleImport("command-line-args").getACall() - or - // `require('meow')(help, {...spec})` => `{a: ..., b: ....}` - this = DataFlow::moduleImport("meow").getACall() - or - // `require("dashdash").createParser(...spec)` => `{a: ..., b: ...}` - this = - [ - API::moduleImport("dashdash"), - API::moduleImport("dashdash").getMember("createParser").getReturn() - ].getMember("parse").getACall() - or - // `require('commander').myCmdArgumentName` - this = commander().getAMember().asSource() - or - // `require('commander').opt()` => `{a: ..., b: ...}` - this = commander().getMember("opts").getACall() - } - } - - /** - * Holds if there is a command line parsing step from `pred` to `succ`. - * E.g: `var succ = require("minimist")(pred)`. - */ - predicate argsParseStep(DataFlow::Node pred, DataFlow::Node succ) { - exists(DataFlow::CallNode call | - call = DataFlow::moduleMember("args", "parse").getACall() or - call = DataFlow::moduleImport(["yargs-parser", "minimist", "subarg"]).getACall() - | - succ = call and - pred = call.getArgument(0) - ) - } - - /** - * Gets a Command instance from the `commander` library. - */ - private API::Node commander() { - result = API::moduleImport("commander") - or - // `require("commander").program === require("commander")` - result = commander().getMember("program") - or - result = commander().getMember("Command").getInstance() - or - // lots of chainable methods - result = commander().getAMember().getReturn() - } - - /** - * Gets an instance of `yargs`. - * Either directly imported as a module, or through some chained method call. - */ - private DataFlow::SourceNode yargs() { - result = DataFlow::moduleImport("yargs") - or - // script used to generate list of chained methods: https://gist.github.com/erik-krogh/f8afe952c0577f4b563a993e613269ba - exists(string method | - not method = - // the methods that does not return a chained `yargs` object. - [ - "getContext", "getDemandedOptions", "getDemandedCommands", "getDeprecatedOptions", - "_getParseContext", "getOptions", "getGroups", "getStrict", "getStrictCommands", - "getExitProcess", "locale", "getUsageInstance", "getCommandInstance" - ] - | - result = yargs().getAMethodCall(method) - ) - } - - /** - * An array of command line arguments (`argv`) parsed by the `yargs` library. - */ - class YargsArgv extends Source { - YargsArgv() { - this = yargs().getAPropertyRead("argv") - or - this = yargs().getAMethodCall("parse") and - this.(DataFlow::MethodCallNode).getNumArgument() = 0 - } - } + private class CommandLineArgumentsAsSource extends Source instanceof CommandLineArguments { } /** * A command-line argument that effectively is system-controlled, and therefore not likely to be exploitable when used in the execution of another command. @@ -193,7 +78,7 @@ module IndirectCommandInjection { SystemControlledCommandLineArgumentSanitizer() { // `process.argv[0]` and `process.argv[1]` are paths to `node` and `main`. exists(string index | index = "0" or index = "1" | - this = any(CommandLineArgumentsArray a).getAPropertyRead(index) + this = DataFlow::globalVarRef("process").getAPropertyRead("argv").getAPropertyRead(index) ) } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/IndirectCommandInjectionQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/IndirectCommandInjectionQuery.qll index d2de26d5cd03..b3e59aec7bd8 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/IndirectCommandInjectionQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/IndirectCommandInjectionQuery.qll @@ -28,8 +28,4 @@ class Configuration extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { this.isSinkWithHighlight(sink, _) } override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer } - - override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { - argsParseStep(pred, succ) - } } diff --git a/javascript/ql/test/library-tests/threat-models/sources/sources.js b/javascript/ql/test/library-tests/threat-models/sources/sources.js index 0194af87f6ec..0e9e73f57be4 100644 --- a/javascript/ql/test/library-tests/threat-models/sources/sources.js +++ b/javascript/ql/test/library-tests/threat-models/sources/sources.js @@ -2,3 +2,43 @@ import 'dummy'; var x = process.env['foo']; // $ threat-source=environment SINK(x); // $ hasFlow + +var y = process.argv[2]; // $ threat-source=commandargs +SINK(y); // $ hasFlow + + +// Accessing command line arguments using yargs +// https://www.npmjs.com/package/yargs/v/17.7.2 +const yargs = require('yargs/yargs'); +const { hideBin } = require('yargs/helpers'); +const argv = yargs(hideBin(process.argv)).argv; // $ threat-source=commandargs + +SINK(argv.foo); // $ MISSING: hasFlow + +// older version +// https://www.npmjs.com/package/yargs/v/7.1.2 +const yargsOld = require('yargs'); +const argvOld = yargsOld.argv; // $ threat-source=commandargs + +SINK(argvOld.foo); // $ hasFlow + +// Accessing command line arguments using yargs-parser +const yargsParser = require('yargs-parser'); +const src = process.argv.slice(2); // $ threat-source=commandargs +const parsedArgs = yargsParser(src); + +SINK(parsedArgs.foo); // $ hasFlow + +// Accessing command line arguments using minimist +const minimist = require('minimist'); +const args = minimist(process.argv.slice(2)); // $ threat-source=commandargs + +SINK(args.foo); // $ hasFlow + + +// Accessing command line arguments using commander +const { Command } = require('commander'); // $ SPURIOUS: threat-source=commandargs +const program = new Command(); +program.parse(process.argv); // $ threat-source=commandargs + +SINK(program.opts().foo); // $ hasFlow SPURIOUS: threat-source=commandargs From d3ae4c930eb92f7904984ce838eeb40e4f1e20ea Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 19 Aug 2024 14:49:26 +0200 Subject: [PATCH 08/21] JS: Model newer `yargs` command-line parsing pattern --- .../lib/semmle/javascript/frameworks/CommandLineArguments.qll | 2 ++ .../ql/test/library-tests/threat-models/sources/sources.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/CommandLineArguments.qll b/javascript/ql/lib/semmle/javascript/frameworks/CommandLineArguments.qll index db1444db7415..50beb04b8879 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/CommandLineArguments.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/CommandLineArguments.qll @@ -74,6 +74,8 @@ private class DefaultModels extends CommandLineArguments::Range { or // `require('commander').opt()` => `{a: ..., b: ...}` this = commander().getMember("opts").getACall() + or + this = API::moduleImport("yargs/yargs").getReturn().getMember("argv").asSource() } } diff --git a/javascript/ql/test/library-tests/threat-models/sources/sources.js b/javascript/ql/test/library-tests/threat-models/sources/sources.js index 0e9e73f57be4..60168992dae7 100644 --- a/javascript/ql/test/library-tests/threat-models/sources/sources.js +++ b/javascript/ql/test/library-tests/threat-models/sources/sources.js @@ -13,7 +13,7 @@ const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); const argv = yargs(hideBin(process.argv)).argv; // $ threat-source=commandargs -SINK(argv.foo); // $ MISSING: hasFlow +SINK(argv.foo); // $ hasFlow // older version // https://www.npmjs.com/package/yargs/v/7.1.2 From 1726287bf401a936ec57e7f8282d1a43db8b8c61 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 19 Aug 2024 15:00:19 +0200 Subject: [PATCH 09/21] JS: Add e2e threat-model test --- .../local-threat-source/SqlInjection.expected | 17 +++++++++++++++++ .../local-threat-source/SqlInjection.ext.yml | 6 ++++++ .../local-threat-source/SqlInjection.qlref | 1 + .../CWE-089/local-threat-source/test.js | 9 +++++++++ 4 files changed, 33 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/SqlInjection.expected create mode 100644 javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/SqlInjection.ext.yml create mode 100644 javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/SqlInjection.qlref create mode 100644 javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/test.js diff --git a/javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/SqlInjection.expected b/javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/SqlInjection.expected new file mode 100644 index 000000000000..05749a54c5be --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/SqlInjection.expected @@ -0,0 +1,17 @@ +nodes +| test.js:4:5:4:29 | temp | +| test.js:4:12:4:22 | process.env | +| test.js:4:12:4:22 | process.env | +| test.js:4:12:4:29 | process.env['foo'] | +| test.js:7:14:7:61 | 'SELECT ... + temp | +| test.js:7:14:7:61 | 'SELECT ... + temp | +| test.js:7:58:7:61 | temp | +edges +| test.js:4:5:4:29 | temp | test.js:7:58:7:61 | temp | +| test.js:4:12:4:22 | process.env | test.js:4:12:4:29 | process.env['foo'] | +| test.js:4:12:4:22 | process.env | test.js:4:12:4:29 | process.env['foo'] | +| test.js:4:12:4:29 | process.env['foo'] | test.js:4:5:4:29 | temp | +| test.js:7:58:7:61 | temp | test.js:7:14:7:61 | 'SELECT ... + temp | +| test.js:7:58:7:61 | temp | test.js:7:14:7:61 | 'SELECT ... + temp | +#select +| test.js:7:14:7:61 | 'SELECT ... + temp | test.js:4:12:4:22 | process.env | test.js:7:14:7:61 | 'SELECT ... + temp | This query string depends on a $@. | test.js:4:12:4:22 | process.env | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/SqlInjection.ext.yml b/javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/SqlInjection.ext.yml new file mode 100644 index 000000000000..63507f477386 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/SqlInjection.ext.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/threat-models + extensible: threatModelConfiguration + data: + - ["local", true, 0] diff --git a/javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/SqlInjection.qlref b/javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/SqlInjection.qlref new file mode 100644 index 000000000000..d1d02cbe8d37 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/SqlInjection.qlref @@ -0,0 +1 @@ +Security/CWE-089/SqlInjection.ql diff --git a/javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/test.js b/javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/test.js new file mode 100644 index 000000000000..42b11b27b6eb --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-089/local-threat-source/test.js @@ -0,0 +1,9 @@ +const mysql = require('mysql'); +const pool = mysql.createPool(getConfig()); + +let temp = process.env['foo']; +pool.getConnection(function(err, connection) { + connection.query({ + sql: 'SELECT * FROM `books` WHERE `author` = ' + temp, // NOT OK + }, function(error, results, fields) {}); +}); From 84f6b89ced8b4e5590fb5f5bc475399431f1810b Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 29 Oct 2024 11:29:27 +0100 Subject: [PATCH 10/21] JS: Minor improvements to threat-model Concepts Mirroring what was done for Python --- javascript/ql/lib/semmle/javascript/Concepts.qll | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/Concepts.qll b/javascript/ql/lib/semmle/javascript/Concepts.qll index 2bb0799f2262..1fbc9f75c3a8 100644 --- a/javascript/ql/lib/semmle/javascript/Concepts.qll +++ b/javascript/ql/lib/semmle/javascript/Concepts.qll @@ -16,6 +16,11 @@ private import codeql.threatmodels.ThreatModels class ThreatModelSource extends DataFlow::Node instanceof ThreatModelSource::Range { /** * Gets a string that represents the source kind with respect to threat modeling. + * + * + * See + * - https://github.com/github/codeql/blob/main/docs/codeql/reusables/threat-model-description.rst + * - https://github.com/github/codeql/blob/main/shared/threat-models/ext/threat-model-grouping.model.yml */ string getThreatModel() { result = super.getThreatModel() } @@ -34,6 +39,10 @@ module ThreatModelSource { abstract class Range extends DataFlow::Node { /** * Gets a string that represents the source kind with respect to threat modeling. + * + * See + * - https://github.com/github/codeql/blob/main/docs/codeql/reusables/threat-model-description.rst + * - https://github.com/github/codeql/blob/main/shared/threat-models/ext/threat-model-grouping.model.yml */ abstract string getThreatModel(); @@ -45,11 +54,11 @@ module ThreatModelSource { /** * A data flow source that is enabled in the current threat model configuration. */ -class ActiveThreatModelSource extends DataFlow::Node { +class ActiveThreatModelSource extends ThreatModelSource { ActiveThreatModelSource() { exists(string kind | currentThreatModel(kind) and - this.(ThreatModelSource).getThreatModel() = kind + this.getThreatModel() = kind ) } } From 07bc1feb118b6a6dd40a953ba1c4b595250889ae Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 29 Oct 2024 11:32:41 +0100 Subject: [PATCH 11/21] Docs: Threat-models supported in JS Capturing - https://github.com/github/codeql/pull/17203/commits/7d3793e7181917bfaad10c71dcc284f18ee79a3a - https://github.com/github/codeql/pull/17203/commits/e35c2b243a5cb101f3d97d89aca34cef222a79c3 - https://github.com/github/codeql/pull/17203/commits/e11bfc27bd20271c1b1c69f7b5ff0e23040b86a4 --- .../customizing-library-models-for-javascript.rst | 9 ++++++++- docs/codeql/reusables/beta-note-threat-models.rst | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/codeql/codeql-language-guides/customizing-library-models-for-javascript.rst b/docs/codeql/codeql-language-guides/customizing-library-models-for-javascript.rst index b062c66bcca8..b4a3446e942f 100644 --- a/docs/codeql/codeql-language-guides/customizing-library-models-for-javascript.rst +++ b/docs/codeql/codeql-language-guides/customizing-library-models-for-javascript.rst @@ -506,7 +506,7 @@ Kinds Source kinds ~~~~~~~~~~~~ -- **remote**: A generic source of remote flow. Most taint-tracking queries will use such a source. Currently this is the only supported source kind. +See documentation below for :ref:`Threat models `. Sink kinds ~~~~~~~~~~ @@ -529,3 +529,10 @@ Summary kinds - **taint**: A summary that propagates taint. This means the output is not necessarily equal to the input, but it was derived from the input in an unrestrictive way. An attacker who controls the input will have significant control over the output as well. - **value**: A summary that preserves the value of the input or creates a copy of the input such that all of its object properties are preserved. + +.. _threat-models-javascript: + +Threat models +------------- + +.. include:: ../reusables/threat-model-description.rst diff --git a/docs/codeql/reusables/beta-note-threat-models.rst b/docs/codeql/reusables/beta-note-threat-models.rst index 9fcca40975a1..a9fdf66589bf 100644 --- a/docs/codeql/reusables/beta-note-threat-models.rst +++ b/docs/codeql/reusables/beta-note-threat-models.rst @@ -2,4 +2,4 @@ Note - Threat models are currently in beta and subject to change. During the beta, threat models are supported only by Java, C# and Python analysis. + Threat models are currently in beta and subject to change. During the beta, threat models are supported only by Java, C#, Python and JavaScript/TypeScript analysis. From 7c7420a9a4a7fb3bf303c031c6134713dfd67c09 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 29 Oct 2024 11:35:56 +0100 Subject: [PATCH 12/21] JS: Add change-note --- javascript/ql/lib/change-notes/2024-10-29-threat-models.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 javascript/ql/lib/change-notes/2024-10-29-threat-models.md diff --git a/javascript/ql/lib/change-notes/2024-10-29-threat-models.md b/javascript/ql/lib/change-notes/2024-10-29-threat-models.md new file mode 100644 index 000000000000..ba01e6f6fbda --- /dev/null +++ b/javascript/ql/lib/change-notes/2024-10-29-threat-models.md @@ -0,0 +1,4 @@ +--- +category: feature +--- +* Added support for custom threat-models, which can be used in most of our taint-tracking queries, see our [documentation](https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#extending-codeql-coverage-with-threat-models) for more details. From 365686469584b0adcc666037fb970fe104ab9721 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 29 Oct 2024 14:57:17 +0100 Subject: [PATCH 13/21] JS: Add `database` threat-model source modeling --- javascript/ql/lib/semmle/javascript/Concepts.qll | 11 +++++++++++ .../library-tests/threat-models/sources/sources.js | 13 +++++++++++++ 2 files changed, 24 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/Concepts.qll b/javascript/ql/lib/semmle/javascript/Concepts.qll index 1fbc9f75c3a8..47c667f778bb 100644 --- a/javascript/ql/lib/semmle/javascript/Concepts.qll +++ b/javascript/ql/lib/semmle/javascript/Concepts.qll @@ -148,6 +148,17 @@ abstract class DatabaseAccess extends DataFlow::Node { } } +/** + * A DatabaseAccess seen as a ThreatModelSource. + */ +private class DatabaseAccessAsThreatModelSource extends ThreatModelSource::Range { + DatabaseAccessAsThreatModelSource() { this = any(DatabaseAccess access).getAResult() } + + override string getThreatModel() { result = "database" } + + override string getSourceType() { result = "DatabaseAccess" } +} + /** * A data flow node that reads persistent data. */ diff --git a/javascript/ql/test/library-tests/threat-models/sources/sources.js b/javascript/ql/test/library-tests/threat-models/sources/sources.js index 60168992dae7..8596b14a786e 100644 --- a/javascript/ql/test/library-tests/threat-models/sources/sources.js +++ b/javascript/ql/test/library-tests/threat-models/sources/sources.js @@ -42,3 +42,16 @@ const program = new Command(); program.parse(process.argv); // $ threat-source=commandargs SINK(program.opts().foo); // $ hasFlow SPURIOUS: threat-source=commandargs + +// ------ reading from database ------ + +// Accessing database using mysql +const mysql = require('mysql'); +const connection = mysql.createConnection({host: 'localhost'}); +connection.connect(); +connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) { // $ threat-source=database + if (error) throw error; + SINK(results); // $ hasFlow + SINK(results[0]); // $ hasFlow + SINK(results[0].solution); // $ hasFlow +}); From 2b6c27eb60eb9952fe750b7b0c69edc3cd67815e Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 29 Oct 2024 15:10:50 +0100 Subject: [PATCH 14/21] JS: Add initial `file` threat-model support However, as indicated by the `MISSING` annotations, we could do better. --- .../ql/lib/semmle/javascript/Concepts.qll | 13 +++++++ .../threat-models/sources/sources.js | 34 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/Concepts.qll b/javascript/ql/lib/semmle/javascript/Concepts.qll index 47c667f778bb..6cab648f5561 100644 --- a/javascript/ql/lib/semmle/javascript/Concepts.qll +++ b/javascript/ql/lib/semmle/javascript/Concepts.qll @@ -122,6 +122,19 @@ abstract class FileSystemReadAccess extends FileSystemAccess { abstract DataFlow::Node getADataNode(); } +/** + * A FileSystemReadAccess seen as a ThreatModelSource. + */ +private class FileSystemReadAccessAsThreatModelSource extends ThreatModelSource::Range { + FileSystemReadAccessAsThreatModelSource() { + this = any(FileSystemReadAccess access).getADataNode() + } + + override string getThreatModel() { result = "file" } + + override string getSourceType() { result = "FileSystemReadAccess" } +} + /** * A data flow node that writes data to the file system. */ diff --git a/javascript/ql/test/library-tests/threat-models/sources/sources.js b/javascript/ql/test/library-tests/threat-models/sources/sources.js index 8596b14a786e..cfda65458cdf 100644 --- a/javascript/ql/test/library-tests/threat-models/sources/sources.js +++ b/javascript/ql/test/library-tests/threat-models/sources/sources.js @@ -55,3 +55,37 @@ connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) { SINK(results[0]); // $ hasFlow SINK(results[0].solution); // $ hasFlow }); + +// ------ reading from file ------ + +// Accessing file contents using fs +const fs = require('fs'); +fs.readFile('file.txt', 'utf8', (err, data) => { // $ MISSING: threat-source=file + SINK(data); // $ MISSING: hasFlow +}); + +// Accessing file contents using fs.readFileSync +const fileContent = fs.readFileSync('file.txt', 'utf8'); // $ threat-source=file +SINK(fileContent); // $ hasFlow + +// Accessing file contents using fs.promises +fs.promises.readFile('file.txt', 'utf8').then((data) => { // $ MISSING: threat-source=file + SINK(data); // $ MISSING: hasFlow +}); + +// Accessing file contents using fs.createReadStream +const readStream = fs.createReadStream('file.txt'); +readStream.on('data', (chunk) => { // $ threat-source=file + SINK(chunk); // $ hasFlow +}); +const data = readStream.read(); // $ threat-source=file +SINK(data); // $ hasFlow + +// using readline +const readline = require('readline'); +const rl_file = readline.createInterface({ + input: fs.createReadStream('file.txt') // $ MISSING: threat-source=file +}); +rl_file.on("line", (line) => { + SINK(line); // $ MISSING: hasFlow +}); From b47fa77dc6a3809b6d32f301cd35ffc0cbc2eb20 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 29 Oct 2024 15:14:15 +0100 Subject: [PATCH 15/21] JS: Add tests for `stdin` threat-model sources --- .../threat-models/sources/sources.js | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/javascript/ql/test/library-tests/threat-models/sources/sources.js b/javascript/ql/test/library-tests/threat-models/sources/sources.js index cfda65458cdf..02a87bd43d9f 100644 --- a/javascript/ql/test/library-tests/threat-models/sources/sources.js +++ b/javascript/ql/test/library-tests/threat-models/sources/sources.js @@ -89,3 +89,32 @@ const rl_file = readline.createInterface({ rl_file.on("line", (line) => { SINK(line); // $ MISSING: hasFlow }); + + +// ------ reading from stdin ------ + +// Accessing stdin using process.stdin +process.stdin.on('data', (data) => { // $ MISSING: threat-source=stdin + SINK(data); // $ MISSING: hasFlow +}); + +const stdin_line = process.stdin.read(); // $ MISSING: threat-source=stdin +SINK(stdin_line); // $ MISSING: hasFlow + +// Accessing stdin using readline +const readline = require('readline'); +const rl_stdin = readline.createInterface({ + input: process.stdin // $ MISSING: threat-source=stdin +}); +rl_stdin.question('', (answer) => { + SINK(answer); // $ MISSING: hasFlow +}); + +function handler(answer) { + SINK(answer); // $ MISSING: hasFlow +} +rl_stdin.question('', handler); + +rl_stdin.on("line", (line) => { + SINK(line); // $ MISSING: hasFlow +}); From 971f53870e675631895a39cb93ec458bd1923532 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 31 Oct 2024 13:29:09 +0100 Subject: [PATCH 16/21] JS: Include `fs` externs Makes a difference due to the modeling of NodeJSFileSystemAccessRead depending on these, see https://github.com/github/codeql/blob/412e841d6929c7a4cf6508a01e721db01df7ac49/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll#L479-L488 File copied from https://github.com/github/codeql/blob/7cef4322e70e10c0c62e9ce933ca9f6db44b6ec1/javascript/externs/nodejs/fs.js --- .../threat-models/sources/externs.js | 1698 +++++++++++++++++ .../threat-models/sources/sources.js | 4 +- 2 files changed, 1700 insertions(+), 2 deletions(-) create mode 100644 javascript/ql/test/library-tests/threat-models/sources/externs.js diff --git a/javascript/ql/test/library-tests/threat-models/sources/externs.js b/javascript/ql/test/library-tests/threat-models/sources/externs.js new file mode 100644 index 000000000000..1afdf83bcd0d --- /dev/null +++ b/javascript/ql/test/library-tests/threat-models/sources/externs.js @@ -0,0 +1,1698 @@ +// Automatically generated from TypeScript type definitions provided by +// DefinitelyTyped (https://github.com/DefinitelyTyped/DefinitelyTyped), +// which is licensed under the MIT license; see file DefinitelyTyped-LICENSE +// in parent directory. +// Type definitions for Node.js 10.5.x +// Project: http://nodejs.org/ +// Definitions by: Microsoft TypeScript +// DefinitelyTyped +// Parambir Singh +// Christian Vaagland Tellnes +// Wilco Bakker +// Nicolas Voigt +// Chigozirim C. +// Flarna +// Mariusz Wiktorczyk +// wwwy3y3 +// Deividas Bakanas +// Kelvin Jin +// Alvis HT Tang +// Sebastian Silbermann +// Hannes Magnusson +// Alberto Schiabel +// Klaus Meinhardt +// Huw +// Nicolas Even +// Bruno Scheufler +// Mohsen Azimi +// Hoàng Văn Khải +// Alexander T. +// Lishude +// Andrew Makarov +// Zane Hannan AU +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +/** + * @externs + * @fileoverview Definitions for module "fs" + */ + +var fs = {}; + +var events = require("events"); + +/** + * @interface + */ +function Stats() {} + +/** + * @return {boolean} + */ +Stats.prototype.isFile = function() {}; + +/** + * @return {boolean} + */ +Stats.prototype.isDirectory = function() {}; + +/** + * @return {boolean} + */ +Stats.prototype.isBlockDevice = function() {}; + +/** + * @return {boolean} + */ +Stats.prototype.isCharacterDevice = function() {}; + +/** + * @return {boolean} + */ +Stats.prototype.isSymbolicLink = function() {}; + +/** + * @return {boolean} + */ +Stats.prototype.isFIFO = function() {}; + +/** + * @return {boolean} + */ +Stats.prototype.isSocket = function() {}; + +/** + * @type {number} + */ +Stats.prototype.dev; + +/** + * @type {number} + */ +Stats.prototype.ino; + +/** + * @type {number} + */ +Stats.prototype.mode; + +/** + * @type {number} + */ +Stats.prototype.nlink; + +/** + * @type {number} + */ +Stats.prototype.uid; + +/** + * @type {number} + */ +Stats.prototype.gid; + +/** + * @type {number} + */ +Stats.prototype.rdev; + +/** + * @type {number} + */ +Stats.prototype.size; + +/** + * @type {number} + */ +Stats.prototype.blksize; + +/** + * @type {number} + */ +Stats.prototype.blocks; + +/** + * @type {Date} + */ +Stats.prototype.atime; + +/** + * @type {Date} + */ +Stats.prototype.mtime; + +/** + * @type {Date} + */ +Stats.prototype.ctime; + +/** + * @type {Date} + */ +Stats.prototype.birthtime; + +/** + * @interface + * @extends {events.EventEmitter} + */ +function FSWatcher() {} + +/** + * @return {void} + */ +FSWatcher.prototype.close = function() {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +FSWatcher.prototype.addListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(string, (string|Buffer)): void)} listener + * @return {*} + */ +FSWatcher.prototype.addListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number, string): void)} listener + * @return {*} + */ +FSWatcher.prototype.addListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +FSWatcher.prototype.on = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(string, (string|Buffer)): void)} listener + * @return {*} + */ +FSWatcher.prototype.on = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number, string): void)} listener + * @return {*} + */ +FSWatcher.prototype.on = function(event, listener) {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +FSWatcher.prototype.once = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(string, (string|Buffer)): void)} listener + * @return {*} + */ +FSWatcher.prototype.once = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number, string): void)} listener + * @return {*} + */ +FSWatcher.prototype.once = function(event, listener) {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +FSWatcher.prototype.prependListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(string, (string|Buffer)): void)} listener + * @return {*} + */ +FSWatcher.prototype.prependListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number, string): void)} listener + * @return {*} + */ +FSWatcher.prototype.prependListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +FSWatcher.prototype.prependOnceListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(string, (string|Buffer)): void)} listener + * @return {*} + */ +FSWatcher.prototype.prependOnceListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number, string): void)} listener + * @return {*} + */ +FSWatcher.prototype.prependOnceListener = function(event, listener) {}; + +/** + * @interface + * @extends {internal.Readable} + */ +fs.ReadStream = function() {}; + +/** + * @return {void} + */ +fs.ReadStream.prototype.close = function() {}; + +/** + * @return {void} + */ +fs.ReadStream.prototype.destroy = function() {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +fs.ReadStream.prototype.addListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number): void)} listener + * @return {*} + */ +fs.ReadStream.prototype.addListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(): void)} listener + * @return {*} + */ +fs.ReadStream.prototype.addListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +fs.ReadStream.prototype.on = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number): void)} listener + * @return {*} + */ +fs.ReadStream.prototype.on = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(): void)} listener + * @return {*} + */ +fs.ReadStream.prototype.on = function(event, listener) {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +fs.ReadStream.prototype.once = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number): void)} listener + * @return {*} + */ +fs.ReadStream.prototype.once = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(): void)} listener + * @return {*} + */ +fs.ReadStream.prototype.once = function(event, listener) {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +fs.ReadStream.prototype.prependListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number): void)} listener + * @return {*} + */ +fs.ReadStream.prototype.prependListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(): void)} listener + * @return {*} + */ +fs.ReadStream.prototype.prependListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +fs.ReadStream.prototype.prependOnceListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number): void)} listener + * @return {*} + */ +fs.ReadStream.prototype.prependOnceListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(): void)} listener + * @return {*} + */ +fs.ReadStream.prototype.prependOnceListener = function(event, listener) {}; + +/** + * @interface + * @extends {internal.Writable} + */ +fs.WriteStream = function() {}; + +/** + * @return {void} + */ +fs.WriteStream.prototype.close = function() {}; + +/** + * @type {number} + */ +fs.WriteStream.prototype.bytesWritten; + +/** + * @type {(string|Buffer)} + */ +fs.WriteStream.prototype.path; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +fs.WriteStream.prototype.addListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number): void)} listener + * @return {*} + */ +fs.WriteStream.prototype.addListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(): void)} listener + * @return {*} + */ +fs.WriteStream.prototype.addListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +fs.WriteStream.prototype.on = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number): void)} listener + * @return {*} + */ +fs.WriteStream.prototype.on = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(): void)} listener + * @return {*} + */ +fs.WriteStream.prototype.on = function(event, listener) {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +fs.WriteStream.prototype.once = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number): void)} listener + * @return {*} + */ +fs.WriteStream.prototype.once = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(): void)} listener + * @return {*} + */ +fs.WriteStream.prototype.once = function(event, listener) {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +fs.WriteStream.prototype.prependListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number): void)} listener + * @return {*} + */ +fs.WriteStream.prototype.prependListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(): void)} listener + * @return {*} + */ +fs.WriteStream.prototype.prependListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {Function} listener + * @return {*} + */ +fs.WriteStream.prototype.prependOnceListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(number): void)} listener + * @return {*} + */ +fs.WriteStream.prototype.prependOnceListener = function(event, listener) {}; + +/** + * @param {string} event + * @param {(function(): void)} listener + * @return {*} + */ +fs.WriteStream.prototype.prependOnceListener = function(event, listener) {}; + +/** + * @param {string} oldPath + * @param {string} newPath + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.rename = function(oldPath, newPath, callback) {}; + +/** + * @param {string} oldPath + * @param {string} newPath + * @return {void} + */ +fs.renameSync = function(oldPath, newPath) {}; + +/** + * @param {(string|Buffer)} path + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.truncate = function(path, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {number} len + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.truncate = function(path, len, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {number=} len + * @return {void} + */ +fs.truncateSync = function(path, len) {}; + +/** + * @param {number} fd + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.ftruncate = function(fd, callback) {}; + +/** + * @param {number} fd + * @param {number} len + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.ftruncate = function(fd, len, callback) {}; + +/** + * @param {number} fd + * @param {number=} len + * @return {void} + */ +fs.ftruncateSync = function(fd, len) {}; + +/** + * @param {(string|Buffer)} path + * @param {number} uid + * @param {number} gid + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.chown = function(path, uid, gid, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {number} uid + * @param {number} gid + * @return {void} + */ +fs.chownSync = function(path, uid, gid) {}; + +/** + * @param {number} fd + * @param {number} uid + * @param {number} gid + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.fchown = function(fd, uid, gid, callback) {}; + +/** + * @param {number} fd + * @param {number} uid + * @param {number} gid + * @return {void} + */ +fs.fchownSync = function(fd, uid, gid) {}; + +/** + * @param {(string|Buffer)} path + * @param {number} uid + * @param {number} gid + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.lchown = function(path, uid, gid, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {number} uid + * @param {number} gid + * @return {void} + */ +fs.lchownSync = function(path, uid, gid) {}; + +/** + * @param {(string|Buffer)} path + * @param {number} mode + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.chmod = function(path, mode, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {string} mode + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.chmod = function(path, mode, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {number} mode + * @return {void} + */ +fs.chmodSync = function(path, mode) {}; + +/** + * @param {(string|Buffer)} path + * @param {string} mode + * @return {void} + */ +fs.chmodSync = function(path, mode) {}; + +/** + * @param {number} fd + * @param {number} mode + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.fchmod = function(fd, mode, callback) {}; + +/** + * @param {number} fd + * @param {string} mode + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.fchmod = function(fd, mode, callback) {}; + +/** + * @param {number} fd + * @param {number} mode + * @return {void} + */ +fs.fchmodSync = function(fd, mode) {}; + +/** + * @param {number} fd + * @param {string} mode + * @return {void} + */ +fs.fchmodSync = function(fd, mode) {}; + +/** + * @param {(string|Buffer)} path + * @param {number} mode + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.lchmod = function(path, mode, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {string} mode + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.lchmod = function(path, mode, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {number} mode + * @return {void} + */ +fs.lchmodSync = function(path, mode) {}; + +/** + * @param {(string|Buffer)} path + * @param {string} mode + * @return {void} + */ +fs.lchmodSync = function(path, mode) {}; + +/** + * @param {(string|Buffer)} path + * @param {(function(NodeJS.ErrnoException, fs.Stats): *)=} callback + * @return {void} + */ +fs.stat = function(path, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {(function(NodeJS.ErrnoException, fs.Stats): *)=} callback + * @return {void} + */ +fs.lstat = function(path, callback) {}; + +/** + * @param {number} fd + * @param {(function(NodeJS.ErrnoException, fs.Stats): *)=} callback + * @return {void} + */ +fs.fstat = function(fd, callback) {}; + +/** + * @param {(string|Buffer)} path + * @return {fs.Stats} + */ +fs.statSync = function(path) {}; + +/** + * @param {(string|Buffer)} path + * @return {fs.Stats} + */ +fs.lstatSync = function(path) {}; + +/** + * @param {number} fd + * @return {fs.Stats} + */ +fs.fstatSync = function(fd) {}; + +/** + * @param {(string|Buffer)} srcpath + * @param {(string|Buffer)} dstpath + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.link = function(srcpath, dstpath, callback) {}; + +/** + * @param {(string|Buffer)} srcpath + * @param {(string|Buffer)} dstpath + * @return {void} + */ +fs.linkSync = function(srcpath, dstpath) {}; + +/** + * @param {(string|Buffer)} srcpath + * @param {(string|Buffer)} dstpath + * @param {string=} type + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.symlink = function(srcpath, dstpath, type, callback) {}; + +/** + * @param {(string|Buffer)} srcpath + * @param {(string|Buffer)} dstpath + * @param {string=} type + * @return {void} + */ +fs.symlinkSync = function(srcpath, dstpath, type) {}; + +/** + * @param {(string|Buffer)} path + * @param {(function(NodeJS.ErrnoException, string): *)=} callback + * @return {void} + */ +fs.readlink = function(path, callback) {}; + +/** + * @param {(string|Buffer)} path + * @return {string} + */ +fs.readlinkSync = function(path) {}; + +/** + * @param {(string|Buffer)} path + * @param {(function(NodeJS.ErrnoException, string): *)=} callback + * @return {void} + */ +fs.realpath = function(path, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {Object} cache + * @param {(function(NodeJS.ErrnoException, string): *)} callback + * @return {void} + */ +fs.realpath = function(path, cache, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {Object=} cache + * @return {string} + */ +fs.realpathSync = function(path, cache) {}; + +/** + * @param {(string|Buffer)} path + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.unlink = function(path, callback) {}; + +/** + * @param {(string|Buffer)} path + * @return {void} + */ +fs.unlinkSync = function(path) {}; + +/** + * @param {(string|Buffer)} path + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.rmdir = function(path, callback) {}; + +/** + * @param {(string|Buffer)} path + * @return {void} + */ +fs.rmdirSync = function(path) {}; + +/** + * @param {(string|Buffer)} path + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.mkdir = function(path, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {number} mode + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.mkdir = function(path, mode, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {string} mode + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.mkdir = function(path, mode, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {number=} mode + * @return {void} + */ +fs.mkdirSync = function(path, mode) {}; + +/** + * @param {(string|Buffer)} path + * @param {string=} mode + * @return {void} + */ +fs.mkdirSync = function(path, mode) {}; + +/** + * @param {string} prefix + * @param {(function(NodeJS.ErrnoException, string): void)=} callback + * @return {void} + */ +fs.mkdtemp = function(prefix, callback) {}; + +/** + * @param {string} prefix + * @return {string} + */ +fs.mkdtempSync = function(prefix) {}; + +/** + * @param {(string|Buffer)} path + * @param {(function(NodeJS.ErrnoException, Array): void)=} callback + * @return {void} + */ +fs.readdir = function(path, callback) {}; + +/** + * @param {(string|Buffer)} path + * @return {Array} + */ +fs.readdirSync = function(path) {}; + +/** + * @param {number} fd + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.close = function(fd, callback) {}; + +/** + * @param {number} fd + * @return {void} + */ +fs.closeSync = function(fd) {}; + +/** + * @param {(string|Buffer)} path + * @param {(string|number)} flags + * @param {(function(NodeJS.ErrnoException, number): void)} callback + * @return {void} + */ +fs.open = function(path, flags, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {(string|number)} flags + * @param {number} mode + * @param {(function(NodeJS.ErrnoException, number): void)} callback + * @return {void} + */ +fs.open = function(path, flags, mode, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {(string|number)} flags + * @param {number=} mode + * @return {number} + */ +fs.openSync = function(path, flags, mode) {}; + +/** + * @param {(string|Buffer)} path + * @param {number} atime + * @param {number} mtime + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.utimes = function(path, atime, mtime, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {Date} atime + * @param {Date} mtime + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.utimes = function(path, atime, mtime, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {number} atime + * @param {number} mtime + * @return {void} + */ +fs.utimesSync = function(path, atime, mtime) {}; + +/** + * @param {(string|Buffer)} path + * @param {Date} atime + * @param {Date} mtime + * @return {void} + */ +fs.utimesSync = function(path, atime, mtime) {}; + +/** + * @param {number} fd + * @param {number} atime + * @param {number} mtime + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.futimes = function(fd, atime, mtime, callback) {}; + +/** + * @param {number} fd + * @param {Date} atime + * @param {Date} mtime + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.futimes = function(fd, atime, mtime, callback) {}; + +/** + * @param {number} fd + * @param {number} atime + * @param {number} mtime + * @return {void} + */ +fs.futimesSync = function(fd, atime, mtime) {}; + +/** + * @param {number} fd + * @param {Date} atime + * @param {Date} mtime + * @return {void} + */ +fs.futimesSync = function(fd, atime, mtime) {}; + +/** + * @param {number} fd + * @param {(function(NodeJS.ErrnoException=): void)=} callback + * @return {void} + */ +fs.fsync = function(fd, callback) {}; + +/** + * @param {number} fd + * @return {void} + */ +fs.fsyncSync = function(fd) {}; + +/** + * @param {number} fd + * @param {Buffer} buffer + * @param {number} offset + * @param {number} length + * @param {number} position + * @param {(function(NodeJS.ErrnoException, number, Buffer): void)=} callback + * @return {void} + */ +fs.write = function(fd, buffer, offset, length, position, callback) {}; + +/** + * @param {number} fd + * @param {Buffer} buffer + * @param {number} offset + * @param {number} length + * @param {(function(NodeJS.ErrnoException, number, Buffer): void)=} callback + * @return {void} + */ +fs.write = function(fd, buffer, offset, length, callback) {}; + +/** + * @param {number} fd + * @param {*} data + * @param {(function(NodeJS.ErrnoException, number, string): void)=} callback + * @return {void} + */ +fs.write = function(fd, data, callback) {}; + +/** + * @param {number} fd + * @param {*} data + * @param {number} offset + * @param {(function(NodeJS.ErrnoException, number, string): void)=} callback + * @return {void} + */ +fs.write = function(fd, data, offset, callback) {}; + +/** + * @param {number} fd + * @param {*} data + * @param {number} offset + * @param {string} encoding + * @param {(function(NodeJS.ErrnoException, number, string): void)=} callback + * @return {void} + */ +fs.write = function(fd, data, offset, encoding, callback) {}; + +/** + * @param {number} fd + * @param {Buffer} buffer + * @param {number} offset + * @param {number} length + * @param {number=} position + * @return {number} + */ +fs.writeSync = function(fd, buffer, offset, length, position) {}; + +/** + * @param {number} fd + * @param {*} data + * @param {number=} position + * @param {string=} enconding + * @return {number} + */ +fs.writeSync = function(fd, data, position, enconding) {}; + +/** + * @param {number} fd + * @param {Buffer} buffer + * @param {number} offset + * @param {number} length + * @param {number} position + * @param {(function(NodeJS.ErrnoException, number, Buffer): void)=} callback + * @return {void} + */ +fs.read = function(fd, buffer, offset, length, position, callback) {}; + +/** + * @param {number} fd + * @param {Buffer} buffer + * @param {number} offset + * @param {number} length + * @param {number} position + * @return {number} + */ +fs.readSync = function(fd, buffer, offset, length, position) {}; + +/** + * @param {string} filename + * @param {string} encoding + * @param {(function(NodeJS.ErrnoException, string): void)} callback + * @return {void} + */ +fs.readFile = function(filename, encoding, callback) {}; + +/** + * @param {string} filename + * @param {{encoding: string, flag: string}} options + * @param {(function(NodeJS.ErrnoException, string): void)} callback + * @return {void} + */ +fs.readFile = function(filename, options, callback) {}; + +/** + * @param {string} filename + * @param {{flag: string}} options + * @param {(function(NodeJS.ErrnoException, Buffer): void)} callback + * @return {void} + */ +fs.readFile = function(filename, options, callback) {}; + +/** + * @param {string} filename + * @param {(function(NodeJS.ErrnoException, Buffer): void)} callback + * @return {void} + */ +fs.readFile = function(filename, callback) {}; + +/** + * @param {string} filename + * @param {string} encoding + * @return {string} + */ +fs.readFileSync = function(filename, encoding) {}; + +/** + * @param {string} filename + * @param {{encoding: string, flag: string}} options + * @return {string} + */ +fs.readFileSync = function(filename, options) {}; + +/** + * @param {string} filename + * @param {{flag: string}=} options + * @return {Buffer} + */ +fs.readFileSync = function(filename, options) {}; + +/** + * @param {string} filename + * @param {*} data + * @param {(function(NodeJS.ErrnoException): void)=} callback + * @return {void} + */ +fs.writeFile = function(filename, data, callback) {}; + +/** + * @param {string} filename + * @param {*} data + * @param {{encoding: string, mode: number, flag: string}} options + * @param {(function(NodeJS.ErrnoException): void)=} callback + * @return {void} + */ +fs.writeFile = function(filename, data, options, callback) {}; + +/** + * @param {string} filename + * @param {*} data + * @param {{encoding: string, mode: string, flag: string}} options + * @param {(function(NodeJS.ErrnoException): void)=} callback + * @return {void} + */ +fs.writeFile = function(filename, data, options, callback) {}; + +/** + * @param {string} filename + * @param {*} data + * @param {{encoding: string, mode: number, flag: string}=} options + * @return {void} + */ +fs.writeFileSync = function(filename, data, options) {}; + +/** + * @param {string} filename + * @param {*} data + * @param {{encoding: string, mode: string, flag: string}=} options + * @return {void} + */ +fs.writeFileSync = function(filename, data, options) {}; + +/** + * @param {string} filename + * @param {*} data + * @param {{encoding: string, mode: number, flag: string}} options + * @param {(function(NodeJS.ErrnoException): void)=} callback + * @return {void} + */ +fs.appendFile = function(filename, data, options, callback) {}; + +/** + * @param {string} filename + * @param {*} data + * @param {{encoding: string, mode: string, flag: string}} options + * @param {(function(NodeJS.ErrnoException): void)=} callback + * @return {void} + */ +fs.appendFile = function(filename, data, options, callback) {}; + +/** + * @param {string} filename + * @param {*} data + * @param {(function(NodeJS.ErrnoException): void)=} callback + * @return {void} + */ +fs.appendFile = function(filename, data, callback) {}; + +/** + * @param {string} filename + * @param {*} data + * @param {{encoding: string, mode: number, flag: string}=} options + * @return {void} + */ +fs.appendFileSync = function(filename, data, options) {}; + +/** + * @param {string} filename + * @param {*} data + * @param {{encoding: string, mode: string, flag: string}=} options + * @return {void} + */ +fs.appendFileSync = function(filename, data, options) {}; + +/** + * @param {string} filename + * @param {(function(fs.Stats, fs.Stats): void)} listener + * @return {void} + */ +fs.watchFile = function(filename, listener) {}; + +/** + * @param {string} filename + * @param {{persistent: boolean, interval: number}} options + * @param {(function(fs.Stats, fs.Stats): void)} listener + * @return {void} + */ +fs.watchFile = function(filename, options, listener) {}; + +/** + * @param {string} filename + * @param {(function(fs.Stats, fs.Stats): void)=} listener + * @return {void} + */ +fs.unwatchFile = function(filename, listener) {}; + +/** + * @param {string} filename + * @param {(function(string, string): *)=} listener + * @return {fs.FSWatcher} + */ +fs.watch = function(filename, listener) {}; + +/** + * @param {string} filename + * @param {string} encoding + * @param {(function(string, (string|Buffer)): *)=} listener + * @return {fs.FSWatcher} + */ +fs.watch = function(filename, encoding, listener) {}; + +/** + * @param {string} filename + * @param {{persistent: boolean, recursive: boolean, encoding: string}} options + * @param {(function(string, (string|Buffer)): *)=} listener + * @return {fs.FSWatcher} + */ +fs.watch = function(filename, options, listener) {}; + +/** + * @param {(string|Buffer)} path + * @param {(function(boolean): void)=} callback + * @return {void} + */ +fs.exists = function(path, callback) {}; + +/** + * @param {(string|Buffer)} path + * @return {boolean} + */ +fs.existsSync = function(path) {}; + +/** + * @interface + */ +function Constants() {} + +/** + * @type {number} + */ +Constants.prototype.F_OK; + +/** + * @type {number} + */ +Constants.prototype.R_OK; + +/** + * @type {number} + */ +Constants.prototype.W_OK; + +/** + * @type {number} + */ +Constants.prototype.X_OK; + +/** + * @type {fs.Constants} + */ +fs.constants; + +/** + * @param {(string|Buffer)} path + * @param {(function(NodeJS.ErrnoException): void)} callback + * @return {void} + */ +fs.access = function(path, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {number} mode + * @param {(function(NodeJS.ErrnoException): void)} callback + * @return {void} + */ +fs.access = function(path, mode, callback) {}; + +/** + * @param {(string|Buffer)} path + * @param {number=} mode + * @return {void} + */ +fs.accessSync = function(path, mode) {}; + +/** + * @param {(string|Buffer)} path + * @param {{flags: string, encoding: string, fd: number, mode: number, autoClose: boolean, start: number, end: number}=} options + * @return {fs.ReadStream} + */ +fs.createReadStream = function(path, options) {}; + +/** + * @param {(string|Buffer)} path + * @param {{flags: string, encoding: string, fd: number, mode: number}=} options + * @return {fs.WriteStream} + */ +fs.createWriteStream = function(path, options) {}; + +/** + * @param {number} fd + * @param {Function} callback + * @return {void} + */ +fs.fdatasync = function(fd, callback) {}; + +/** + * @param {number} fd + * @return {void} + */ +fs.fdatasyncSync = function(fd) {}; + +module.exports.ReadStream = fs.ReadStream; + +module.exports.WriteStream = fs.WriteStream; + +module.exports.rename = fs.rename; + +module.exports.renameSync = fs.renameSync; + +module.exports.truncate = fs.truncate; + +module.exports.truncate = fs.truncate; + +module.exports.truncateSync = fs.truncateSync; + +module.exports.ftruncate = fs.ftruncate; + +module.exports.ftruncate = fs.ftruncate; + +module.exports.ftruncateSync = fs.ftruncateSync; + +module.exports.chown = fs.chown; + +module.exports.chownSync = fs.chownSync; + +module.exports.fchown = fs.fchown; + +module.exports.fchownSync = fs.fchownSync; + +module.exports.lchown = fs.lchown; + +module.exports.lchownSync = fs.lchownSync; + +module.exports.chmod = fs.chmod; + +module.exports.chmod = fs.chmod; + +module.exports.chmodSync = fs.chmodSync; + +module.exports.chmodSync = fs.chmodSync; + +module.exports.fchmod = fs.fchmod; + +module.exports.fchmod = fs.fchmod; + +module.exports.fchmodSync = fs.fchmodSync; + +module.exports.fchmodSync = fs.fchmodSync; + +module.exports.lchmod = fs.lchmod; + +module.exports.lchmod = fs.lchmod; + +module.exports.lchmodSync = fs.lchmodSync; + +module.exports.lchmodSync = fs.lchmodSync; + +module.exports.stat = fs.stat; + +module.exports.lstat = fs.lstat; + +module.exports.fstat = fs.fstat; + +module.exports.statSync = fs.statSync; + +module.exports.lstatSync = fs.lstatSync; + +module.exports.fstatSync = fs.fstatSync; + +module.exports.link = fs.link; + +module.exports.linkSync = fs.linkSync; + +module.exports.symlink = fs.symlink; + +module.exports.symlinkSync = fs.symlinkSync; + +module.exports.readlink = fs.readlink; + +module.exports.readlinkSync = fs.readlinkSync; + +module.exports.realpath = fs.realpath; + +module.exports.realpath = fs.realpath; + +module.exports.realpathSync = fs.realpathSync; + +module.exports.unlink = fs.unlink; + +module.exports.unlinkSync = fs.unlinkSync; + +module.exports.rmdir = fs.rmdir; + +module.exports.rmdirSync = fs.rmdirSync; + +module.exports.mkdir = fs.mkdir; + +module.exports.mkdir = fs.mkdir; + +module.exports.mkdir = fs.mkdir; + +module.exports.mkdirSync = fs.mkdirSync; + +module.exports.mkdirSync = fs.mkdirSync; + +module.exports.mkdtemp = fs.mkdtemp; + +module.exports.mkdtempSync = fs.mkdtempSync; + +module.exports.readdir = fs.readdir; + +module.exports.readdirSync = fs.readdirSync; + +module.exports.close = fs.close; + +module.exports.closeSync = fs.closeSync; + +module.exports.open = fs.open; + +module.exports.open = fs.open; + +module.exports.openSync = fs.openSync; + +module.exports.utimes = fs.utimes; + +module.exports.utimes = fs.utimes; + +module.exports.utimesSync = fs.utimesSync; + +module.exports.utimesSync = fs.utimesSync; + +module.exports.futimes = fs.futimes; + +module.exports.futimes = fs.futimes; + +module.exports.futimesSync = fs.futimesSync; + +module.exports.futimesSync = fs.futimesSync; + +module.exports.fsync = fs.fsync; + +module.exports.fsyncSync = fs.fsyncSync; + +module.exports.write = fs.write; + +module.exports.write = fs.write; + +module.exports.write = fs.write; + +module.exports.write = fs.write; + +module.exports.write = fs.write; + +module.exports.writeSync = fs.writeSync; + +module.exports.writeSync = fs.writeSync; + +module.exports.read = fs.read; + +module.exports.readSync = fs.readSync; + +module.exports.readFile = fs.readFile; + +module.exports.readFile = fs.readFile; + +module.exports.readFile = fs.readFile; + +module.exports.readFile = fs.readFile; + +module.exports.readFileSync = fs.readFileSync; + +module.exports.readFileSync = fs.readFileSync; + +module.exports.readFileSync = fs.readFileSync; + +module.exports.writeFile = fs.writeFile; + +module.exports.writeFile = fs.writeFile; + +module.exports.writeFile = fs.writeFile; + +module.exports.writeFileSync = fs.writeFileSync; + +module.exports.writeFileSync = fs.writeFileSync; + +module.exports.appendFile = fs.appendFile; + +module.exports.appendFile = fs.appendFile; + +module.exports.appendFile = fs.appendFile; + +module.exports.appendFileSync = fs.appendFileSync; + +module.exports.appendFileSync = fs.appendFileSync; + +module.exports.watchFile = fs.watchFile; + +module.exports.watchFile = fs.watchFile; + +module.exports.unwatchFile = fs.unwatchFile; + +module.exports.watch = fs.watch; + +module.exports.watch = fs.watch; + +module.exports.watch = fs.watch; + +module.exports.exists = fs.exists; + +module.exports.existsSync = fs.existsSync; + +module.exports.constants = fs.constants; + +module.exports.access = fs.access; + +module.exports.access = fs.access; + +module.exports.accessSync = fs.accessSync; + +module.exports.createReadStream = fs.createReadStream; + +module.exports.createWriteStream = fs.createWriteStream; + +module.exports.fdatasync = fs.fdatasync; + +module.exports.fdatasyncSync = fs.fdatasyncSync; + +/** + * @param {string} path + * @param {(number|Date)} atime + * @param {(number|Date)} mtime + * @param {number=} flags + * @param {Function=} callback + * @return {void} + */ +fs.utimensat = function(path, atime, mtime, flags, callback) {}; + +/** + * @param {string} path + * @param {(number|Date)} atime + * @param {(number|Date)} mtime + * @param {number=} flags + * @return {void} + */ +fs.utimensatSync = function(path, atime, mtime, flags) {}; + +/** + * @param {*} fd + * @param {(number|Date)} atime + * @param {(number|Date)} mtime + * @param {number=} flags + * @param {Function=} callback + * @return {void} + */ +fs.futimensat = function(fd, atime, mtime, flags, callback) {}; + +/** + * @param {*} fd + * @param {(number|Date)} atime + * @param {(number|Date)} mtime + * @param {number=} flags + * @return {void} + */ +fs.futimensatSync = function(fd, atime, mtime, flags) {}; + +/** + * @constructor + * @extends {internal.Writable} + */ +fs.SyncWriteStream; + +/** + * @type {number} + */ +fs.F_OK; + +/** + * @type {number} + */ +fs.R_OK; + +/** + * @type {number} + */ +fs.W_OK; + +/** + * @type {number} + */ +fs.X_OK; + +module.exports.utimensat = fs.utimensat; + +module.exports.utimensatSync = fs.utimensatSync; + +module.exports.futimensat = fs.futimensat; + +module.exports.futimensatSync = fs.futimensatSync; + +module.exports.SyncWriteStream = fs.SyncWriteStream; + +module.exports.F_OK = fs.F_OK; + +module.exports.R_OK = fs.R_OK; + +module.exports.W_OK = fs.W_OK; + +module.exports.X_OK = fs.X_OK; diff --git a/javascript/ql/test/library-tests/threat-models/sources/sources.js b/javascript/ql/test/library-tests/threat-models/sources/sources.js index 02a87bd43d9f..7df621f726eb 100644 --- a/javascript/ql/test/library-tests/threat-models/sources/sources.js +++ b/javascript/ql/test/library-tests/threat-models/sources/sources.js @@ -60,8 +60,8 @@ connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) { // Accessing file contents using fs const fs = require('fs'); -fs.readFile('file.txt', 'utf8', (err, data) => { // $ MISSING: threat-source=file - SINK(data); // $ MISSING: hasFlow +fs.readFile('file.txt', 'utf8', (err, data) => { // $ threat-source=file + SINK(data); // $ hasFlow }); // Accessing file contents using fs.readFileSync From 34b86c39c1052b61e9de89e81fb19588428eb677 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 31 Oct 2024 14:09:38 +0100 Subject: [PATCH 17/21] JS: Model fs.promises.readFile as file source You could argue that proper modeling be done in the same way as `NodeJSFileSystemAccessRead` is done for the callback based `fs` API (in NodeJSLib.qll). However, that work is straying from the core goals I'm working towards right now, so I'll argue that "perfect is the enemy of good", and leave this as is for now. --- .../lib/semmle/javascript/frameworks/NodeJSLib.model.yml | 8 ++++++++ .../test/library-tests/threat-models/sources/sources.js | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml diff --git a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml new file mode 100644 index 000000000000..d27366a1257d --- /dev/null +++ b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml @@ -0,0 +1,8 @@ +extensions: + # Make sure that the extensible model predicates have at least one definition + # to avoid errors about undefined extensionals. + - addsTo: + pack: codeql/javascript-all + extensible: sourceModel + data: + - ['fs', 'Member[promises].Member[readFile].ReturnValue.Member[then].Argument[0].Parameter[0]', 'file'] diff --git a/javascript/ql/test/library-tests/threat-models/sources/sources.js b/javascript/ql/test/library-tests/threat-models/sources/sources.js index 7df621f726eb..51f1c8b192b3 100644 --- a/javascript/ql/test/library-tests/threat-models/sources/sources.js +++ b/javascript/ql/test/library-tests/threat-models/sources/sources.js @@ -69,8 +69,8 @@ const fileContent = fs.readFileSync('file.txt', 'utf8'); // $ threat-source=file SINK(fileContent); // $ hasFlow // Accessing file contents using fs.promises -fs.promises.readFile('file.txt', 'utf8').then((data) => { // $ MISSING: threat-source=file - SINK(data); // $ MISSING: hasFlow +fs.promises.readFile('file.txt', 'utf8').then((data) => { // $ threat-source=file + SINK(data); // $ hasFlow }); // Accessing file contents using fs.createReadStream From eca8bf5a35ec2ec5635c458db82b2e5876313421 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 31 Oct 2024 14:24:27 +0100 Subject: [PATCH 18/21] JS: Do simple modeling of `process.stdin` as threat-model source --- .../lib/semmle/javascript/frameworks/NodeJSLib.model.yml | 2 ++ .../test/library-tests/threat-models/sources/sources.js | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml index d27366a1257d..4b8f80ff2d46 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml +++ b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml @@ -6,3 +6,5 @@ extensions: extensible: sourceModel data: - ['fs', 'Member[promises].Member[readFile].ReturnValue.Member[then].Argument[0].Parameter[0]', 'file'] + - ['global', 'Member[process].Member[stdin].Member[read].ReturnValue', 'stdin'] + - ['global', 'Member[process].Member[stdin].Member[on,addListener].WithStringArgument[0=data].Argument[1].Parameter[0]', 'stdin'] diff --git a/javascript/ql/test/library-tests/threat-models/sources/sources.js b/javascript/ql/test/library-tests/threat-models/sources/sources.js index 51f1c8b192b3..e8df6f3c50dd 100644 --- a/javascript/ql/test/library-tests/threat-models/sources/sources.js +++ b/javascript/ql/test/library-tests/threat-models/sources/sources.js @@ -94,12 +94,12 @@ rl_file.on("line", (line) => { // ------ reading from stdin ------ // Accessing stdin using process.stdin -process.stdin.on('data', (data) => { // $ MISSING: threat-source=stdin - SINK(data); // $ MISSING: hasFlow +process.stdin.on('data', (data) => { // $ threat-source=stdin + SINK(data); // $ hasFlow }); -const stdin_line = process.stdin.read(); // $ MISSING: threat-source=stdin -SINK(stdin_line); // $ MISSING: hasFlow +const stdin_line = process.stdin.read(); // $ threat-source=stdin +SINK(stdin_line); // $ hasFlow // Accessing stdin using readline const readline = require('readline'); From 61e60de969d2454619f89bf05213e254a9d71e04 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 31 Oct 2024 14:29:30 +0100 Subject: [PATCH 19/21] JS: Model `readline` as a `stdin` threat-model source Technically not always true, but my assumption is that +90% of the time that's what it will be used for, so while we could be more precise by adding a taint-step from the `input` part of the construction, I'm not sure it's worth it in this case. Furthermore, doing so would break with the current way we model threat-model sources, and how sources are generally modeled in JS... so for a very pretty setup it would require changing all the other `file` threat-model sources to start at the constructors such as `fs.createReadStream()` and have taint-propagation steps towards the actual use (like we do in Python)... I couldn't see an easy path forwards for doing this while keeping the Concepts integration, so I opted for the simpler solution here. --- .../javascript/frameworks/NodeJSLib.model.yml | 2 ++ .../threat-models/sources/sources.js | 20 +++++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml index 4b8f80ff2d46..80d91236ca98 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml +++ b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml @@ -8,3 +8,5 @@ extensions: - ['fs', 'Member[promises].Member[readFile].ReturnValue.Member[then].Argument[0].Parameter[0]', 'file'] - ['global', 'Member[process].Member[stdin].Member[read].ReturnValue', 'stdin'] - ['global', 'Member[process].Member[stdin].Member[on,addListener].WithStringArgument[0=data].Argument[1].Parameter[0]', 'stdin'] + - ['readline', 'Member[createInterface].ReturnValue.Member[question].Argument[1].Parameter[0]', 'stdin'] + - ['readline', 'Member[createInterface].ReturnValue.Member[on,addListener].WithStringArgument[0=line].Argument[1].Parameter[0]', 'stdin'] diff --git a/javascript/ql/test/library-tests/threat-models/sources/sources.js b/javascript/ql/test/library-tests/threat-models/sources/sources.js index e8df6f3c50dd..a87e4335de43 100644 --- a/javascript/ql/test/library-tests/threat-models/sources/sources.js +++ b/javascript/ql/test/library-tests/threat-models/sources/sources.js @@ -84,10 +84,10 @@ SINK(data); // $ hasFlow // using readline const readline = require('readline'); const rl_file = readline.createInterface({ - input: fs.createReadStream('file.txt') // $ MISSING: threat-source=file + input: fs.createReadStream('file.txt') }); -rl_file.on("line", (line) => { - SINK(line); // $ MISSING: hasFlow +rl_file.on("line", (line) => { // $ SPURIOUS: threat-source=stdin MISSING: threat-source=file + SINK(line); // $ hasFlow }); @@ -104,17 +104,17 @@ SINK(stdin_line); // $ hasFlow // Accessing stdin using readline const readline = require('readline'); const rl_stdin = readline.createInterface({ - input: process.stdin // $ MISSING: threat-source=stdin + input: process.stdin }); -rl_stdin.question('', (answer) => { - SINK(answer); // $ MISSING: hasFlow +rl_stdin.question('', (answer) => { // $ threat-source=stdin + SINK(answer); // $ hasFlow }); -function handler(answer) { - SINK(answer); // $ MISSING: hasFlow +function handler(answer) { // $ threat-source=stdin + SINK(answer); // $ hasFlow } rl_stdin.question('', handler); -rl_stdin.on("line", (line) => { - SINK(line); // $ MISSING: hasFlow +rl_stdin.on("line", (line) => { // $ threat-source=stdin + SINK(line); // $ hasFlow }); From 19fae76a94ead68af76a901e912b9864dd8e4c4b Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 1 Nov 2024 10:24:22 +0100 Subject: [PATCH 20/21] JS: Remove dummy comment Co-authored-by: Asger F --- .../ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml index 80d91236ca98..43035615a126 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml +++ b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.model.yml @@ -1,6 +1,4 @@ extensions: - # Make sure that the extensible model predicates have at least one definition - # to avoid errors about undefined extensionals. - addsTo: pack: codeql/javascript-all extensible: sourceModel From dc8e645594812dd071759633a6a72c6336a46558 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 1 Nov 2024 10:47:10 +0100 Subject: [PATCH 21/21] JS: Convert remaining queries to use ActiveThreatModelSourceAsSource --- .../ClientSideUrlRedirectCustomizations.qll | 13 ++++++++++--- .../CommandInjectionCustomizations.qll | 13 ++++++++++--- ...figurationForCredentialsCustomizations.qll | 13 ++++++++++--- .../RegExpInjectionCustomizations.qll | 12 ++++++++---- .../dataflow/RequestForgeryCustomizations.qll | 15 ++++++++++++--- .../ResourceExhaustionCustomizations.qll | 13 ++++++++++--- .../dataflow/TaintedPathCustomizations.qll | 19 +++++++++---------- ...sPermissiveConfigurationCustomizations.qll | 13 ++++++++++--- 8 files changed, 79 insertions(+), 32 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll index 3a0c65f295da..e1d7f0a22c2c 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll @@ -38,9 +38,16 @@ module ClientSideUrlRedirect { DocumentUrl() { this = "document.url" } } - /** A source of remote user input, considered as a flow source for unvalidated URL redirects. */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { - RemoteFlowSourceAsSource() { not this.(ClientSideRemoteFlowSource).getKind().isPath() } + /** + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource { + ActiveThreatModelSourceAsSource() { not this.(ClientSideRemoteFlowSource).getKind().isPath() } override DataFlow::FlowLabel getAFlowLabel() { if this.(ClientSideRemoteFlowSource).getKind().isUrl() diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll index 8581a5b0cb0b..132a5cc2edab 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll @@ -25,9 +25,16 @@ module CommandInjection { */ abstract class Sanitizer extends DataFlow::Node { } - /** A source of remote user input, considered as a flow source for command injection. */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { - RemoteFlowSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } + /** + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource { + ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } override string getSourceType() { result = "a user-provided value" } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/CorsMisconfigurationForCredentialsCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/CorsMisconfigurationForCredentialsCustomizations.qll index 690bc61e14b7..54da0a8709f1 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/CorsMisconfigurationForCredentialsCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/CorsMisconfigurationForCredentialsCustomizations.qll @@ -27,9 +27,16 @@ module CorsMisconfigurationForCredentials { */ abstract class Sanitizer extends DataFlow::Node { } - /** A source of remote user input, considered as a flow source for CORS misconfiguration. */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { - RemoteFlowSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } + /** + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource { + ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } } /** diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RegExpInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RegExpInjectionCustomizations.qll index 87fbbbd5b932..291d6eebc1c6 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RegExpInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RegExpInjectionCustomizations.qll @@ -26,11 +26,15 @@ module RegExpInjection { abstract class Sanitizer extends DataFlow::Node { } /** - * A source of remote user input, considered as a flow source for regular - * expression injection. + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { - RemoteFlowSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } + private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource { + ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } } private import IndirectCommandInjectionCustomizations diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll index 5714de221acc..6d2b5e2ce7b5 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll @@ -39,9 +39,18 @@ module RequestForgery { */ abstract class Sanitizer extends DataFlow::Node { } - /** A source of server-side remote user input, considered as a flow source for request forgery. */ - private class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { - RemoteFlowSourceAsSource() { not this.(ClientSideRemoteFlowSource).getKind().isPathOrUrl() } + /** + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource { + ActiveThreatModelSourceAsSource() { + not this.(ClientSideRemoteFlowSource).getKind().isPathOrUrl() + } override predicate isServerSide() { not this instanceof ClientSideRemoteFlowSource } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ResourceExhaustionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ResourceExhaustionCustomizations.qll index 8307c1f6f939..147d725ae9ae 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ResourceExhaustionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ResourceExhaustionCustomizations.qll @@ -31,9 +31,16 @@ module ResourceExhaustion { */ abstract class Sanitizer extends DataFlow::Node { } - /** A source of remote user input, considered as a data flow source for resource exhaustion vulnerabilities. */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { - RemoteFlowSourceAsSource() { + /** + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource { + ActiveThreatModelSourceAsSource() { // exclude source that only happen client-side not this instanceof ClientSideRemoteFlowSource and not this = DataFlow::parameterNode(any(PostMessageEventHandler pmeh).getEventParameter()) diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index 73615bfd78b5..c24ea7f61100 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -572,16 +572,15 @@ module TaintedPath { } /** - * A source of remote user input, considered as a flow source for - * tainted-path vulnerabilities. - */ - class RemoteFlowSourceAsSource extends Source { - RemoteFlowSourceAsSource() { - exists(RemoteFlowSource src | - this = src and - not src instanceof ClientSideRemoteFlowSource - ) - } + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource { + ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } } /** diff --git a/javascript/ql/src/experimental/Security/CWE-942/CorsPermissiveConfigurationCustomizations.qll b/javascript/ql/src/experimental/Security/CWE-942/CorsPermissiveConfigurationCustomizations.qll index 045d1c1ef549..103872847a0f 100644 --- a/javascript/ql/src/experimental/Security/CWE-942/CorsPermissiveConfigurationCustomizations.qll +++ b/javascript/ql/src/experimental/Security/CWE-942/CorsPermissiveConfigurationCustomizations.qll @@ -25,9 +25,16 @@ module CorsPermissiveConfiguration { */ abstract class Sanitizer extends DataFlow::Node { } - /** A source of remote user input, considered as a flow source for CORS misconfiguration. */ - class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource { - RemoteFlowSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } + /** + * DEPRECATED: Use `ActiveThreatModelSource` from Concepts instead! + */ + deprecated class RemoteFlowSourceAsSource = ActiveThreatModelSourceAsSource; + + /** + * An active threat-model source, considered as a flow source. + */ + private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource { + ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } } /** A flow label representing `true` and `null` values. */