diff --git a/docs/codeql/reusables/threat-model-description.rst b/docs/codeql/reusables/threat-model-description.rst index 05a88c9e90ce..ff1718fcedcc 100644 --- a/docs/codeql/reusables/threat-model-description.rst +++ b/docs/codeql/reusables/threat-model-description.rst @@ -15,5 +15,6 @@ The less commonly used categories are: - ``database-access-result`` which represents a database access. Currently only used by JavaScript. - ``file-write`` which represents opening a file in write mode. Currently only used in C#. - ``reverse-dns`` which represents reverse DNS lookups. Currently only used in Java. +- ``view-component-input`` which represents inputs to a React, Vue, or Angular component (also known as "props"). Currently only used by JavaScript/TypeScript. When running a CodeQL analysis, the ``remote`` threat model is included by default. You can optionally include other threat models as appropriate when using the CodeQL CLI and in GitHub code scanning. For more information, see `Analyzing your code with CodeQL queries `__ and `Customizing your advanced setup for code scanning `__. diff --git a/javascript/ql/lib/semmle/javascript/Concepts.qll b/javascript/ql/lib/semmle/javascript/Concepts.qll index 6cab648f5561..3fce9f6f34a0 100644 --- a/javascript/ql/lib/semmle/javascript/Concepts.qll +++ b/javascript/ql/lib/semmle/javascript/Concepts.qll @@ -26,6 +26,11 @@ class ThreatModelSource extends DataFlow::Node instanceof ThreatModelSource::Ran /** Gets a string that describes the type of this threat-model source. */ string getSourceType() { result = super.getSourceType() } + + /** + * Holds if this is a source of data that is specific to the web browser environment. + */ + predicate isClientSideSource() { super.isClientSideSource() } } /** Provides a class for modeling new sources for specific threat-models. */ @@ -48,6 +53,11 @@ module ThreatModelSource { /** Gets a string that describes the type of this threat-model source. */ abstract string getSourceType(); + + /** + * Holds if this is a source of data that is specific to the web browser environment. + */ + predicate isClientSideSource() { this.getThreatModel() = "view-component-input" } } } diff --git a/javascript/ql/lib/semmle/javascript/ViewComponentInput.qll b/javascript/ql/lib/semmle/javascript/ViewComponentInput.qll new file mode 100644 index 000000000000..bc80826de5c9 --- /dev/null +++ b/javascript/ql/lib/semmle/javascript/ViewComponentInput.qll @@ -0,0 +1,47 @@ +/** + * Provides a classes and predicates for contributing to the `view-component-input` threat model. + */ + +private import javascript + +/** + * An input to a view component, such as React props. + */ +abstract class ViewComponentInput extends DataFlow::Node { + /** Gets a string that describes the type of this threat-model source. */ + abstract string getSourceType(); +} + +private class ViewComponentInputAsThreatModelSource extends ThreatModelSource::Range instanceof ViewComponentInput +{ + ViewComponentInputAsThreatModelSource() { not isSafeType(this.asExpr().getType()) } + + final override string getThreatModel() { result = "view-component-input" } + + final override string getSourceType() { result = ViewComponentInput.super.getSourceType() } +} + +private predicate isSafeType(Type t) { + t instanceof NumberLikeType + or + t instanceof BooleanLikeType + or + t instanceof UndefinedType + or + t instanceof NullType + or + t instanceof VoidType + or + hasSafeTypes(t, t.(UnionType).getNumElementType()) + or + isSafeType(t.(IntersectionType).getAnElementType()) +} + +/** Hold if the first `n` components of `t` are safe types. */ +private predicate hasSafeTypes(UnionType t, int n) { + isSafeType(t.getElementType(0)) and + n = 1 + or + isSafeType(t.getElementType(n - 1)) and + hasSafeTypes(t, n - 1) +} diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Angular2.qll b/javascript/ql/lib/semmle/javascript/frameworks/Angular2.qll index 3ac3d947aced..6c52001ac527 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Angular2.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Angular2.qll @@ -8,6 +8,7 @@ private import semmle.javascript.security.dataflow.CodeInjectionCustomizations private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations private import semmle.javascript.DynamicPropertyAccess private import semmle.javascript.dataflow.internal.PreCallGraphStep +private import semmle.javascript.ViewComponentInput /** * Provides classes for working with Angular (also known as Angular 2.x) applications. @@ -575,4 +576,17 @@ module Angular2 { ) } } + + private class InputFieldAsViewComponentInput extends ViewComponentInput { + InputFieldAsViewComponentInput() { + this = + API::moduleImport("@angular/core") + .getMember("Input") + .getReturn() + .getADecoratedMember() + .asSource() + } + + override string getSourceType() { result = "Angular component input field" } + } } diff --git a/javascript/ql/lib/semmle/javascript/frameworks/React.qll b/javascript/ql/lib/semmle/javascript/frameworks/React.qll index 64c9f5feb782..5ac0e419e264 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/React.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/React.qll @@ -5,6 +5,7 @@ import javascript private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps private import semmle.javascript.dataflow.internal.PreCallGraphStep +private import semmle.javascript.ViewComponentInput /** * Gets a reference to the 'React' object. @@ -868,3 +869,9 @@ private class PropsFlowStep extends PreCallGraphStep { ) } } + +private class ReactPropAsViewComponentInput extends ViewComponentInput { + ReactPropAsViewComponentInput() { this = any(ReactComponent c).getADirectPropsAccess() } + + override string getSourceType() { result = "React props" } +} diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll b/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll index ebfe042f4d06..faba601df52d 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Vue.qll @@ -3,6 +3,7 @@ */ import javascript +import semmle.javascript.ViewComponentInput module Vue { /** The global variable `Vue`, as an API graph entry point. */ @@ -85,17 +86,16 @@ module Vue { * A class with a `@Component` decorator, making it usable as an "options" object in Vue. */ class ClassComponent extends DataFlow::ClassNode { + private ClassDefinition cls; DataFlow::Node decorator; ClassComponent() { - exists(ClassDefinition cls | - this = cls.flow() and - cls.getADecorator().getExpression() = decorator.asExpr() and - ( - componentDecorator().flowsTo(decorator) - or - componentDecorator().getACall() = decorator - ) + this = cls.flow() and + cls.getADecorator().getExpression() = decorator.asExpr() and + ( + componentDecorator().flowsTo(decorator) + or + componentDecorator().getACall() = decorator ) } @@ -105,6 +105,9 @@ module Vue { * These options correspond to the options one would pass to `new Vue({...})` or similar. */ API::Node getDecoratorOptions() { result = decorator.(API::CallNode).getParameter(0) } + + /** Gets the AST node for the class definition. */ + ClassDefinition getClassDefinition() { result = cls } } private string memberKindVerb(DataFlow::MemberKind kind) { @@ -460,6 +463,12 @@ module Vue { SingleFileComponent() { this = MkSingleFileComponent(file) } + /** Gets a call to `defineProps` in this component. */ + DataFlow::CallNode getDefinePropsCall() { + result = DataFlow::globalVarRef("defineProps").getACall() and + result.getFile() = file + } + override Template::Element getTemplateElement() { exists(HTML::Element e | result.(Template::HtmlElement).getElement() = e | e.getFile() = file and @@ -697,4 +706,68 @@ module Vue { override ClientSideRemoteFlowKind getKind() { result = kind } } + + /** + * Holds if the given type annotation indicates a value that is not typically considered taintable. + */ + private predicate isSafeType(TypeAnnotation type) { + type.isBooleany() or + type.isNumbery() or + type.isRawFunction() or + type instanceof FunctionTypeExpr + } + + /** + * Holds if the given field has a type that indicates that is can not contain a taintable value. + */ + private predicate isSafeField(FieldDeclaration field) { isSafeType(field.getTypeAnnotation()) } + + private DataFlow::Node getPropSpec(Component component) { + result = component.getOption("props") + or + result = component.(SingleFileComponent).getDefinePropsCall().getArgument(0) + } + + /** + * Holds if `component` has an input prop with the given name, that is of a taintable type. + */ + private predicate hasTaintableProp(Component component, string name) { + exists(DataFlow::SourceNode spec | spec = getPropSpec(component).getALocalSource() | + spec.(DataFlow::ArrayCreationNode).getAnElement().getStringValue() = name + or + exists(DataFlow::PropWrite write | + write = spec.getAPropertyWrite(name) and + not DataFlow::globalVarRef(["Number", "Boolean"]).flowsTo(write.getRhs()) + ) + ) + or + exists(FieldDeclaration field | + field = component.getAsClassComponent().getClassDefinition().getField(name) and + DataFlow::moduleMember("vue-property-decorator", "Prop") + .getACall() + .flowsToExpr(field.getADecorator().getExpression()) and + not isSafeField(field) + ) + or + // defineProps() can be called with only type arguments and then the Vue compiler will + // infer the prop types. + exists(CallExpr call, FieldDeclaration field | + call = component.(SingleFileComponent).getDefinePropsCall().asExpr() and + field = call.getTypeArgument(0).(InterfaceTypeExpr).getMember(name) and + not isSafeField(field) + ) + } + + private class PropAsViewComponentInput extends ViewComponentInput { + PropAsViewComponentInput() { + exists(Component component, string name | hasTaintableProp(component, name) | + this = component.getAnInstanceRef().getAPropertyRead(name) + or + // defineProps() returns the props + this = component.(SingleFileComponent).getDefinePropsCall().getAPropertyRead(name) + ) + } + + override string getSourceType() { result = "Vue prop" } + } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll index 132a5cc2edab..9762c1142b99 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/CommandInjectionCustomizations.qll @@ -34,7 +34,7 @@ module CommandInjection { * An active threat-model source, considered as a flow source. */ private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource { - ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } + ActiveThreatModelSourceAsSource() { not this.isClientSideSource() } 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 54da0a8709f1..eaf78fd4c4c9 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/CorsMisconfigurationForCredentialsCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/CorsMisconfigurationForCredentialsCustomizations.qll @@ -36,7 +36,7 @@ module CorsMisconfigurationForCredentials { * An active threat-model source, considered as a flow source. */ private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource { - ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } + ActiveThreatModelSourceAsSource() { not this.isClientSideSource() } } /** diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/LogInjectionQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/LogInjectionQuery.qll index 607af3d0f163..9f2060709059 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/LogInjectionQuery.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/LogInjectionQuery.qll @@ -54,7 +54,7 @@ deprecated class LogInjectionConfiguration extends TaintTracking::Configuration * A source of remote user controlled input. */ class RemoteSource extends Source instanceof RemoteFlowSource { - RemoteSource() { not this instanceof ClientSideRemoteFlowSource } + RemoteSource() { not this.isClientSideSource() } } /** diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RegExpInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RegExpInjectionCustomizations.qll index 03cbd01b630d..1c056935d407 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RegExpInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RegExpInjectionCustomizations.qll @@ -34,7 +34,7 @@ module RegExpInjection { * An active threat-model source, considered as a flow source. */ private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource { - ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } + ActiveThreatModelSourceAsSource() { not this.isClientSideSource() } } private import IndirectCommandInjectionCustomizations diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll index aad00b2d22e5..58600c579a84 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RemoteFlowSources.qll @@ -24,12 +24,18 @@ private module Cached { /** * A source of remote input in a web browser environment. + * + * Note that this does not include `view-component-input` sources even if that threat model has been enabled by the user. + * Consider using the predicate `ThreatModelSource#isClientSideSource()` to check for a broader class of client-side sources. */ cached abstract class ClientSideRemoteFlowSource extends RemoteFlowSource { /** Gets a string indicating what part of the browser environment this was derived from. */ cached abstract ClientSideRemoteFlowKind getKind(); + + cached + final override predicate isClientSideSource() { any() } } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll index 6d2b5e2ce7b5..6cc6f6e798c0 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/RequestForgeryCustomizations.qll @@ -52,7 +52,7 @@ module RequestForgery { not this.(ClientSideRemoteFlowSource).getKind().isPathOrUrl() } - override predicate isServerSide() { not this instanceof ClientSideRemoteFlowSource } + override predicate isServerSide() { not super.isClientSideSource() } } /** diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/ResourceExhaustionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/ResourceExhaustionCustomizations.qll index c62aedd4b5cc..e24de9f4d6f9 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/ResourceExhaustionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/ResourceExhaustionCustomizations.qll @@ -63,7 +63,7 @@ module ResourceExhaustion { private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource { ActiveThreatModelSourceAsSource() { // exclude source that only happen client-side - not this instanceof ClientSideRemoteFlowSource and + not this.isClientSideSource() 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 0107d36d63d2..dc23b895a4f6 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -719,7 +719,7 @@ module TaintedPath { * An active threat-model source, considered as a flow source. */ private class ActiveThreatModelSourceAsSource extends Source instanceof ActiveThreatModelSource { - ActiveThreatModelSourceAsSource() { not this instanceof ClientSideRemoteFlowSource } + ActiveThreatModelSourceAsSource() { not this.isClientSideSource() } } /** diff --git a/javascript/ql/src/change-notes/2025-01-22-view-component-inputs.md b/javascript/ql/src/change-notes/2025-01-22-view-component-inputs.md new file mode 100644 index 000000000000..9f25ba446a1b --- /dev/null +++ b/javascript/ql/src/change-notes/2025-01-22-view-component-inputs.md @@ -0,0 +1,7 @@ +--- +category: majorAnalysis +--- +* Added a new threat model kind called `view-component-input`, which can enabled with [advanced setup](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). + When enabled, all React props, Vue props, and input fields in an Angular component are seen as taint sources, even if none of the corresponding instantiation sites appear to pass in a tainted value. + Some users may prefer this as a "defense in depth" option but note that it may result in false positives. + Regardless of whether the threat model is enabled, CodeQL will propagate taint from the instantiation sites of such components into the components themselves. diff --git a/javascript/ql/src/meta/alerts/TaintSources.ql b/javascript/ql/src/meta/alerts/TaintSources.ql index 95daa2a71e28..327aca84460c 100644 --- a/javascript/ql/src/meta/alerts/TaintSources.ql +++ b/javascript/ql/src/meta/alerts/TaintSources.ql @@ -11,13 +11,6 @@ import javascript import meta.internal.TaintMetrics -string getName(DataFlow::Node node) { - result = node.(RemoteFlowSource).getSourceType() - or - not node instanceof RemoteFlowSource and - result = "Taint source" -} - -from DataFlow::Node node -where node = relevantTaintSource() -select node, getName(node) +from ThreatModelSource node +where node = relevantTaintSource() and node.getThreatModel() = "remote" +select node, getTaintSourceName(node) diff --git a/javascript/ql/src/meta/alerts/ThreatModelSources.ql b/javascript/ql/src/meta/alerts/ThreatModelSources.ql new file mode 100644 index 000000000000..99cd7b342a02 --- /dev/null +++ b/javascript/ql/src/meta/alerts/ThreatModelSources.ql @@ -0,0 +1,19 @@ +/** + * @name Threat model sources + * @description Sources of possibly untrusted input that can be configured via threat models. + * @kind problem + * @problem.severity recommendation + * @id js/meta/alerts/threat-model-sources + * @tags meta + * @precision very-low + */ + +import javascript +import meta.internal.TaintMetrics + +from ThreatModelSource node, string threatModel +where + node = relevantTaintSource() and + threatModel = node.getThreatModel() and + threatModel != "remote" // "remote" is reported by TaintSources.ql +select node, getTaintSourceName(node) + " (\"" + threatModel + "\" threat model)" diff --git a/javascript/ql/src/meta/internal/TaintMetrics.qll b/javascript/ql/src/meta/internal/TaintMetrics.qll index f6eae2eaa6e9..70d63a8cc3aa 100644 --- a/javascript/ql/src/meta/internal/TaintMetrics.qll +++ b/javascript/ql/src/meta/internal/TaintMetrics.qll @@ -75,9 +75,9 @@ DataFlow::Node relevantTaintSink(string kind) { DataFlow::Node relevantTaintSink() { result = relevantTaintSink(_) } /** - * Gets a relevant remote flow source. + * Gets a relevant threat model source. */ -RemoteFlowSource relevantTaintSource() { not result.getFile() instanceof IgnoredFile } +ThreatModelSource relevantTaintSource() { not result.getFile() instanceof IgnoredFile } /** * Gets the output of a call that shows intent to sanitize a value @@ -100,3 +100,10 @@ DataFlow::Node relevantSanitizerInput() { result = any(HtmlSanitizerCall call).getInput() and not result.getFile() instanceof IgnoredFile } + +string getTaintSourceName(DataFlow::Node node) { + result = node.(ThreatModelSource).getSourceType() + or + not node instanceof ThreatModelSource and + result = "Taint source" +} diff --git a/javascript/ql/test/library-tests/frameworks/Angular2/sink.component.ts b/javascript/ql/test/library-tests/frameworks/Angular2/sink.component.ts index 4db18a7e2d6d..7210e235d5a7 100644 --- a/javascript/ql/test/library-tests/frameworks/Angular2/sink.component.ts +++ b/javascript/ql/test/library-tests/frameworks/Angular2/sink.component.ts @@ -1,4 +1,4 @@ -import { Component } from "@angular/core"; +import { Component, Input } from "@angular/core"; import { DomSanitizer } from '@angular/platform-browser'; @Component({ @@ -6,17 +6,17 @@ import { DomSanitizer } from '@angular/platform-browser'; template: "not important" }) export class SinkComponent { - sink1: string; - sink2: string; - sink3: string; - sink4: string; - sink5: string; - sink6: string; - sink7: string; - sink8: string; - sink9: string; + @Input() sink1: string; + @Input() sink2: string; + @Input() sink3: string; + @Input() sink4: string; + @Input() sink5: string; + @Input() sink6: string; + @Input() sink7: string; + @Input() sink8: string; + @Input() sink9: string; - constructor(private sanitizer: DomSanitizer) {} + constructor(private sanitizer: DomSanitizer) { } foo() { this.sanitizer.bypassSecurityTrustHtml(this.sink1); diff --git a/javascript/ql/test/library-tests/frameworks/Angular2/test.expected b/javascript/ql/test/library-tests/frameworks/Angular2/test.expected index acf97ab947e5..4934597c4015 100644 --- a/javascript/ql/test/library-tests/frameworks/Angular2/test.expected +++ b/javascript/ql/test/library-tests/frameworks/Angular2/test.expected @@ -36,3 +36,13 @@ taintFlow | source.component.ts:16:33:16:40 | source() | sink.component.ts:22:48:22:57 | this.sink1 | testAttrSourceLocation | inline.component.ts:8:43:8:60 | [testAttr]=taint | inline.component.ts:8:55:8:59 | | +threatModelSource +| sink.component.ts:22:48:22:57 | this.sink1 | view-component-input | +| sink.component.ts:23:48:23:57 | this.sink2 | view-component-input | +| sink.component.ts:24:48:24:57 | this.sink3 | view-component-input | +| sink.component.ts:25:48:25:57 | this.sink4 | view-component-input | +| sink.component.ts:26:48:26:57 | this.sink5 | view-component-input | +| sink.component.ts:27:48:27:57 | this.sink6 | view-component-input | +| sink.component.ts:28:48:28:57 | this.sink7 | view-component-input | +| sink.component.ts:29:48:29:57 | this.sink8 | view-component-input | +| sink.component.ts:30:48:30:57 | this.sink9 | view-component-input | diff --git a/javascript/ql/test/library-tests/frameworks/Angular2/test.ql b/javascript/ql/test/library-tests/frameworks/Angular2/test.ql index 140ae675a777..91c4dfe11a88 100644 --- a/javascript/ql/test/library-tests/frameworks/Angular2/test.ql +++ b/javascript/ql/test/library-tests/frameworks/Angular2/test.ql @@ -42,3 +42,7 @@ deprecated class LegacyConfig extends TaintTracking::Configuration { } deprecated import utils.test.LegacyDataFlowDiff::DataFlowDiff + +query predicate threatModelSource(ThreatModelSource source, string kind) { + kind = source.getThreatModel() +} diff --git a/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected b/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected index 4804e30b6f79..9a5c38ddbf90 100644 --- a/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected @@ -318,3 +318,28 @@ test_JsxName_this | thisAccesses.js:61:19:61:41 | | thisAccesses.js:61:20:61:23 | this | locationSource | importedComponent.jsx:3:32:3:39 | location | +threatModelSource +| es5.js:4:24:4:33 | this.props | view-component-input | +| es5.js:20:24:20:33 | this.props | view-component-input | +| es6.js:1:37:1:36 | args | view-component-input | +| es6.js:3:24:3:33 | this.props | view-component-input | +| exportedComponent.jsx:1:29:1:33 | props | view-component-input | +| importedComponent.jsx:3:24:3:40 | {color, location} | view-component-input | +| importedComponent.jsx:3:32:3:39 | location | remote | +| namedImport.js:3:27:3:26 | args | view-component-input | +| namedImport.js:5:19:5:18 | args | view-component-input | +| plainfn.js:1:16:1:20 | props | view-component-input | +| plainfn.js:5:17:5:21 | props | view-component-input | +| plainfn.js:9:17:9:21 | props | view-component-input | +| plainfn.js:20:28:20:32 | props | view-component-input | +| preact.js:1:38:1:37 | args | view-component-input | +| preact.js:2:12:2:16 | props | view-component-input | +| preact.js:9:38:9:37 | args | view-component-input | +| probably-a-component.js:1:31:1:30 | args | view-component-input | +| probably-a-component.js:3:9:3:18 | this.props | view-component-input | +| props.js:2:37:2:36 | args | view-component-input | +| props.js:26:16:26:20 | props | view-component-input | +| rare-lifecycle-methods.js:1:33:1:32 | args | view-component-input | +| statePropertyWrites.js:38:24:38:33 | this.props | view-component-input | +| thisAccesses.js:31:12:31:16 | props | view-component-input | +| thisAccesses.js:48:18:48:18 | y | view-component-input | diff --git a/javascript/ql/test/library-tests/frameworks/ReactJS/tests.ql b/javascript/ql/test/library-tests/frameworks/ReactJS/tests.ql index b97e69b2526a..4d20306d4ed4 100644 --- a/javascript/ql/test/library-tests/frameworks/ReactJS/tests.ql +++ b/javascript/ql/test/library-tests/frameworks/ReactJS/tests.ql @@ -11,3 +11,7 @@ import ReactComponent_getAPropRead import ReactName query DataFlow::SourceNode locationSource() { result = DOM::locationSource() } + +query predicate threatModelSource(ThreatModelSource source, string kind) { + kind = source.getThreatModel() +} diff --git a/javascript/ql/test/library-tests/frameworks/Vue/single-component-file-1.vue b/javascript/ql/test/library-tests/frameworks/Vue/single-component-file-1.vue index 4f7e4b9c47fe..02045c31d29a 100644 --- a/javascript/ql/test/library-tests/frameworks/Vue/single-component-file-1.vue +++ b/javascript/ql/test/library-tests/frameworks/Vue/single-component-file-1.vue @@ -3,7 +3,8 @@ diff --git a/javascript/ql/test/library-tests/frameworks/Vue/single-file-component-7.vue b/javascript/ql/test/library-tests/frameworks/Vue/single-file-component-7.vue new file mode 100644 index 000000000000..0d099dc8b9b5 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Vue/single-file-component-7.vue @@ -0,0 +1,12 @@ + + + diff --git a/javascript/ql/test/library-tests/frameworks/Vue/single-file-component-8.vue b/javascript/ql/test/library-tests/frameworks/Vue/single-file-component-8.vue new file mode 100644 index 000000000000..03a7b838f2bf --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/Vue/single-file-component-8.vue @@ -0,0 +1,12 @@ + + + diff --git a/javascript/ql/test/library-tests/frameworks/Vue/tests.expected b/javascript/ql/test/library-tests/frameworks/Vue/tests.expected index 7f0ea1aa9ac5..4fe66404c782 100644 --- a/javascript/ql/test/library-tests/frameworks/Vue/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/Vue/tests.expected @@ -1,12 +1,14 @@ component_getAPropertyValue | compont-with-route.vue:0:0:0:0 | compont-with-route.vue | dataA | compont-with-route.vue:31:14:31:34 | this.$r ... ery.foo | | compont-with-route.vue:0:0:0:0 | compont-with-route.vue | message | compont-with-route.vue:19:15:19:22 | 'Hello!' | -| single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue | dataA | single-component-file-1.vue:6:40:6:41 | 42 | -| single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | dataA | single-file-component-3-script.js:4:37:4:38 | 42 | -| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | dataA | single-file-component-4.vue:15:14:15:15 | 42 | -| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | message | single-file-component-4.vue:12:23:12:30 | 'Hello!' | -| single-file-component-5.vue:0:0:0:0 | single-file-component-5.vue | dataA | single-file-component-5.vue:13:14:13:15 | 42 | -| single-file-component-5.vue:0:0:0:0 | single-file-component-5.vue | message | single-file-component-5.vue:10:23:10:30 | 'Hello!' | +| single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue | dataA | single-component-file-1.vue:7:40:7:54 | 42 + this.input | +| single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | dataA | single-file-component-3-script.js:5:37:5:51 | 42 + this.input | +| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | dataA | single-file-component-4.vue:17:14:17:15 | 42 | +| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | dataB | single-file-component-4.vue:21:14:21:23 | this.input | +| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | message | single-file-component-4.vue:13:23:13:30 | 'Hello!' | +| single-file-component-5.vue:0:0:0:0 | single-file-component-5.vue | dataA | single-file-component-5.vue:15:14:15:15 | 42 | +| single-file-component-5.vue:0:0:0:0 | single-file-component-5.vue | dataB | single-file-component-5.vue:19:14:19:23 | this.input | +| single-file-component-5.vue:0:0:0:0 | single-file-component-5.vue | message | single-file-component-5.vue:11:23:11:30 | 'Hello!' | | tst.js:3:1:10:2 | new Vue ... 2\\n\\t}\\n}) | dataA | tst.js:8:10:8:11 | 42 | | tst.js:12:1:16:2 | new Vue ... \\t}),\\n}) | dataA | tst.js:14:10:14:11 | 42 | | tst.js:18:1:27:2 | Vue.com ... }\\n\\t}\\n}) | dataA | tst.js:20:10:20:11 | 42 | @@ -35,9 +37,11 @@ component_getAPropertyValue | tst.js:113:17:117:2 | base.ex ... 0\\n\\t}\\n}) | fromSubclass2 | tst.js:115:18:115:20 | 100 | component_getOption | compont-with-route.vue:0:0:0:0 | compont-with-route.vue | watch | compont-with-route.vue:10:12:16:5 | {\\n ... }\\n } | -| single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue | data | single-component-file-1.vue:6:11:6:45 | functio ... 42 } } | -| single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | data | single-file-component-3-script.js:4:8:4:42 | functio ... 42 } } | -| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | render | single-file-component-4.vue:9:13:9:22 | (h) => { } | +| single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue | data | single-component-file-1.vue:7:11:7:58 | functio ... put } } | +| single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue | props | single-component-file-1.vue:6:12:6:20 | ['input'] | +| single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | data | single-file-component-3-script.js:5:8:5:55 | functio ... put } } | +| single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | props | single-file-component-3-script.js:4:9:4:17 | ['input'] | +| single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | render | single-file-component-4.vue:10:13:10:22 | (h) => { } | | tst.js:3:1:10:2 | new Vue ... 2\\n\\t}\\n}) | data | tst.js:7:8:9:2 | {\\n\\t\\tdataA: 42\\n\\t} | | tst.js:3:1:10:2 | new Vue ... 2\\n\\t}\\n}) | render | tst.js:4:10:6:2 | functio ... c);\\n\\t} | | tst.js:12:1:16:2 | new Vue ... \\t}),\\n}) | data | tst.js:13:8:15:3 | () => ( ... 42\\n\\t}) | @@ -76,6 +80,9 @@ component | single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue | | single-file-component-4.vue:0:0:0:0 | single-file-component-4.vue | | single-file-component-5.vue:0:0:0:0 | single-file-component-5.vue | +| single-file-component-6.vue:0:0:0:0 | single-file-component-6.vue | +| single-file-component-7.vue:0:0:0:0 | single-file-component-7.vue | +| single-file-component-8.vue:0:0:0:0 | single-file-component-8.vue | | special-syntax.vue:0:0:0:0 | special-syntax.vue | | tst.js:3:1:10:2 | new Vue ... 2\\n\\t}\\n}) | | tst.js:12:1:16:2 | new Vue ... \\t}),\\n}) | @@ -97,10 +104,10 @@ component | tst.js:113:17:117:2 | base.ex ... 0\\n\\t}\\n}) | viewComponentStep | compont-with-route.vue:31:14:31:34 | this.$r ... ery.foo | compont-with-route.vue:2:8:2:21 | v-html=dataA | -| single-component-file-1.vue:6:40:6:41 | 42 | single-component-file-1.vue:2:8:2:21 | v-html=dataA | -| single-file-component-3-script.js:4:37:4:38 | 42 | single-file-component-3.vue:2:8:2:21 | v-html=dataA | -| single-file-component-4.vue:15:14:15:15 | 42 | single-file-component-4.vue:2:8:2:21 | v-html=dataA | -| single-file-component-5.vue:13:14:13:15 | 42 | single-file-component-5.vue:2:8:2:21 | v-html=dataA | +| single-component-file-1.vue:7:40:7:54 | 42 + this.input | single-component-file-1.vue:2:8:2:21 | v-html=dataA | +| single-file-component-3-script.js:5:37:5:51 | 42 + this.input | single-file-component-3.vue:2:8:2:21 | v-html=dataA | +| single-file-component-4.vue:17:14:17:15 | 42 | single-file-component-4.vue:2:8:2:21 | v-html=dataA | +| single-file-component-5.vue:15:14:15:15 | 42 | single-file-component-5.vue:2:8:2:21 | v-html=dataA | | tst.js:100:18:100:19 | 42 | tst.js:102:20:102:29 | this.dataA | templateElement | compont-with-route.vue:1:1:3:11 |