mirror of
https://github.com/github/codeql.git
synced 2026-04-30 03:05:15 +02:00
Merge pull request #5199 from asgerf/js/vue-router
Approved by erik-krogh
This commit is contained in:
@@ -1263,6 +1263,7 @@ module DataFlow {
|
||||
/**
|
||||
* Gets the data flow node corresponding to `e`.
|
||||
*/
|
||||
pragma[inline]
|
||||
ExprNode exprNode(Expr e) { result = valueNode(e) }
|
||||
|
||||
/** Gets the data flow node corresponding to `ssa`. */
|
||||
|
||||
@@ -19,6 +19,9 @@ private import internal.CallGraphs
|
||||
*/
|
||||
class ExprNode extends DataFlow::ValueNode {
|
||||
override Expr astNode;
|
||||
|
||||
pragma[nomagic]
|
||||
ExprNode() { any() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,7 +35,8 @@ module Vue {
|
||||
result =
|
||||
[
|
||||
"beforeCreate", "created", "beforeMount", "mounted", "beforeUpdate", "updated", "activated",
|
||||
"deactivated", "beforeDestroy", "destroyed", "errorCaptured"
|
||||
"deactivated", "beforeDestroy", "destroyed", "errorCaptured", "beforeRouteEnter",
|
||||
"beforeRouteUpdate", "beforeRouteLeave"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -162,6 +163,13 @@ module Vue {
|
||||
result = getAsClassComponent().getDecoratorOption(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a source node flowing into the option `name` of this instance, including those from
|
||||
* extended objects and mixins.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
DataFlow::SourceNode getOptionSource(string name) { result = getOption(name).getALocalSource() }
|
||||
|
||||
/**
|
||||
* Gets the template element used by this instance, if any.
|
||||
*/
|
||||
@@ -189,13 +197,15 @@ module Vue {
|
||||
/**
|
||||
* Gets the node for the `template` option of this instance.
|
||||
*/
|
||||
DataFlow::Node getTemplate() { result = getOption("template") }
|
||||
pragma[nomagic]
|
||||
DataFlow::SourceNode getTemplate() { result = getOptionSource("template") }
|
||||
|
||||
/**
|
||||
* Gets the node for the `render` option of this instance.
|
||||
*/
|
||||
DataFlow::Node getRender() {
|
||||
result = getOption("render")
|
||||
pragma[nomagic]
|
||||
DataFlow::SourceNode getRender() {
|
||||
result = getOptionSource("render")
|
||||
or
|
||||
result = getAsClassComponent().getInstanceMethod("render")
|
||||
}
|
||||
@@ -203,22 +213,38 @@ module Vue {
|
||||
/**
|
||||
* Gets the node for the `methods` option of this instance.
|
||||
*/
|
||||
DataFlow::Node getMethods() { result = getOption("methods") }
|
||||
pragma[nomagic]
|
||||
DataFlow::SourceNode getMethods() { result = getOptionSource("methods") }
|
||||
|
||||
/**
|
||||
* Gets the node for the `computed` option of this instance.
|
||||
*/
|
||||
DataFlow::Node getComputed() { result = getOption("computed") }
|
||||
pragma[nomagic]
|
||||
DataFlow::SourceNode getComputed() { result = getOptionSource("computed") }
|
||||
|
||||
/**
|
||||
* Gets the node for the `watch` option of this instance.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
DataFlow::SourceNode getWatch() { result = getOptionSource("watch") }
|
||||
|
||||
/**
|
||||
* Gets the function responding to changes to the given `propName`.
|
||||
*/
|
||||
DataFlow::FunctionNode getWatchHandler(string propName) {
|
||||
exists(DataFlow::SourceNode watcher | watcher = getWatch().getAPropertySource(propName) |
|
||||
result = watcher
|
||||
or
|
||||
result = watcher.getAPropertySource("handler")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node for a member of the `methods` option of this instance.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private DataFlow::Node getAMethod() {
|
||||
exists(DataFlow::ObjectLiteralNode methods |
|
||||
methods.flowsTo(getMethods()) and
|
||||
result = methods.getAPropertyWrite().getRhs()
|
||||
)
|
||||
pragma[nomagic]
|
||||
private DataFlow::SourceNode getAMethod() {
|
||||
result = getMethods().getAPropertySource()
|
||||
or
|
||||
result = getAsClassComponent().getAnInstanceMethod() and
|
||||
not result = getAsClassComponent().getInstanceMethod([lifecycleHookName(), "render", "data"])
|
||||
@@ -227,19 +253,11 @@ module Vue {
|
||||
/**
|
||||
* Gets a node for a member of the `computed` option of this instance that matches `kind`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private DataFlow::Node getAnAccessor(DataFlow::MemberKind kind) {
|
||||
exists(DataFlow::ObjectLiteralNode computedObj, DataFlow::Node accessorObjOrGetter |
|
||||
computedObj.flowsTo(getComputed()) and
|
||||
computedObj.getAPropertyWrite().getRhs() = accessorObjOrGetter
|
||||
|
|
||||
result = accessorObjOrGetter and kind = DataFlow::MemberKind::getter()
|
||||
or
|
||||
exists(DataFlow::ObjectLiteralNode accessorObj |
|
||||
accessorObj.flowsTo(accessorObjOrGetter) and
|
||||
result = accessorObj.getAPropertyWrite(memberKindVerb(kind)).getRhs()
|
||||
)
|
||||
)
|
||||
pragma[nomagic]
|
||||
private DataFlow::SourceNode getAnAccessor(DataFlow::MemberKind kind) {
|
||||
result = getComputed().getAPropertySource() and kind = DataFlow::MemberKind::getter()
|
||||
or
|
||||
result = getComputed().getAPropertySource().getAPropertySource(memberKindVerb(kind))
|
||||
or
|
||||
result = getAsClassComponent().getAnInstanceMember(kind) and
|
||||
kind.isAccessor()
|
||||
@@ -248,18 +266,10 @@ module Vue {
|
||||
/**
|
||||
* Gets a node for a member `name` of the `computed` option of this instance that matches `kind`.
|
||||
*/
|
||||
private DataFlow::Node getAccessor(string name, DataFlow::MemberKind kind) {
|
||||
exists(DataFlow::ObjectLiteralNode computedObj, DataFlow::SourceNode accessorObjOrGetter |
|
||||
computedObj.flowsTo(getComputed()) and
|
||||
accessorObjOrGetter.flowsTo(computedObj.getAPropertyWrite(name).getRhs())
|
||||
|
|
||||
result = accessorObjOrGetter and kind = DataFlow::MemberKind::getter()
|
||||
or
|
||||
exists(DataFlow::ObjectLiteralNode accessorObj |
|
||||
accessorObj.flowsTo(accessorObjOrGetter) and
|
||||
result = accessorObj.getAPropertyWrite(memberKindVerb(kind)).getRhs()
|
||||
)
|
||||
)
|
||||
private DataFlow::SourceNode getAccessor(string name, DataFlow::MemberKind kind) {
|
||||
result = getComputed().getAPropertySource(name) and kind = DataFlow::MemberKind::getter()
|
||||
or
|
||||
result = getComputed().getAPropertySource(name).getAPropertySource(memberKindVerb(kind))
|
||||
or
|
||||
result = getAsClassComponent().getInstanceMember(name, kind) and
|
||||
kind.isAccessor()
|
||||
@@ -268,11 +278,11 @@ module Vue {
|
||||
/**
|
||||
* Gets the node for the life cycle hook of the `hookName` option of this instance.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private DataFlow::Node getALifecycleHook(string hookName) {
|
||||
pragma[nomagic]
|
||||
DataFlow::SourceNode getALifecycleHook(string hookName) {
|
||||
hookName = lifecycleHookName() and
|
||||
(
|
||||
result = getOption(hookName)
|
||||
result = getOptionSource(hookName)
|
||||
or
|
||||
result = getAsClassComponent().getInstanceMethod(hookName)
|
||||
)
|
||||
@@ -281,16 +291,21 @@ module Vue {
|
||||
/**
|
||||
* Gets a node for a function that will be invoked with `this` bound to this instance.
|
||||
*/
|
||||
DataFlow::Node getABoundFunction() {
|
||||
DataFlow::FunctionNode getABoundFunction() {
|
||||
result = getAMethod()
|
||||
or
|
||||
result = getAnAccessor(_)
|
||||
or
|
||||
result = getALifecycleHook(_)
|
||||
or
|
||||
result = getOptionSource(_)
|
||||
or
|
||||
result = getOptionSource(_).getAPropertySource()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node for the value for property `name` of this instance.
|
||||
* Gets the data flow node that flows into the property `name` of this instance, or is
|
||||
* returned form a getter defining that property.
|
||||
*/
|
||||
DataFlow::Node getAPropertyValue(string name) {
|
||||
exists(DataFlow::SourceNode obj | obj.getAPropertyWrite(name).getRhs() = result |
|
||||
@@ -552,4 +567,67 @@ module Vue {
|
||||
HTML::Element getElement() { result = elem }
|
||||
}
|
||||
}
|
||||
|
||||
/** An API node referring to a `RouteConfig` being passed to `vue-router`. */
|
||||
private API::Node routeConfig() {
|
||||
result = API::moduleImport("vue-router").getParameter(0).getMember("routes").getAMember()
|
||||
or
|
||||
result = routeConfig().getMember("children").getAMember()
|
||||
}
|
||||
|
||||
/** Gets a data flow node that refers to a `Route` object from `vue-router`. */
|
||||
private DataFlow::SourceNode routeObject(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
exists(API::Node router | router = API::moduleImport("vue-router") |
|
||||
result = router.getInstance().getMember("currentRoute").getAnImmediateUse()
|
||||
or
|
||||
result =
|
||||
router
|
||||
.getInstance()
|
||||
.getMember(["beforeEach", "beforeResolve", "afterEach"])
|
||||
.getParameter(0)
|
||||
.getParameter([0, 1])
|
||||
.getAnImmediateUse()
|
||||
or
|
||||
result =
|
||||
router
|
||||
.getParameter(0)
|
||||
.getMember("scrollBehavior")
|
||||
.getParameter([0, 1])
|
||||
.getAnImmediateUse()
|
||||
)
|
||||
or
|
||||
result = routeConfig().getMember("beforeEnter").getParameter([0, 1]).getAnImmediateUse()
|
||||
or
|
||||
exists(Instance i |
|
||||
result = i.getABoundFunction().getAFunctionValue().getReceiver().getAPropertyRead("$route")
|
||||
or
|
||||
result =
|
||||
i.getALifecycleHook(["beforeRouteEnter", "beforeRouteUpdate", "beforeRouteLeave"])
|
||||
.getAFunctionValue()
|
||||
.getParameter([0, 1])
|
||||
or
|
||||
result = i.getWatchHandler("$route").getParameter([0, 1])
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = routeObject(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a data flow node that refers to a `Route` object from `vue-router`. */
|
||||
DataFlow::SourceNode routeObject() { result = routeObject(DataFlow::TypeTracker::end()) }
|
||||
|
||||
private class VueRouterFlowSource extends RemoteFlowSource {
|
||||
VueRouterFlowSource() {
|
||||
this = routeObject().getAPropertyRead(["params", "query", "hash", "path", "fullPath"])
|
||||
or
|
||||
exists(Instance i, string prop |
|
||||
this = i.getWatchHandler(prop).getParameter([0, 1]) and
|
||||
prop.regexpMatch("\\$route\\.(params|query|hash|path|fullPath)\\b.*")
|
||||
)
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "Vue route parameter" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<p v-html="dataA"/>
|
||||
</template>
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import Component from 'vue-class-component'
|
||||
import { router } from './router';
|
||||
|
||||
@Component({
|
||||
watch: {
|
||||
'$route.params.id': {
|
||||
deep: true,
|
||||
handler(newId, oldId) { }
|
||||
},
|
||||
$route(to, from) { }
|
||||
}
|
||||
})
|
||||
export default class MyComponent extends Vue {
|
||||
message = 'Hello!'
|
||||
|
||||
sources() {
|
||||
this.$route.params.x;
|
||||
this.$route.query.x;
|
||||
this.$route.hash.x;
|
||||
this.$route.path;
|
||||
this.$route.fullPath;
|
||||
router.currentRoute.query.x;
|
||||
}
|
||||
|
||||
get dataA() {
|
||||
return this.$route.query.foo; // NOT OK
|
||||
}
|
||||
|
||||
beforeRouteEnter(to, from, next) {
|
||||
to.query.x;
|
||||
from.query.x;
|
||||
}
|
||||
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
to.query.x;
|
||||
from.query.x;
|
||||
}
|
||||
|
||||
beforeRouteLeave(to, from, next) {
|
||||
to.query.x;
|
||||
from.query.x;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
</style>
|
||||
37
javascript/ql/test/library-tests/frameworks/Vue/router.js
Normal file
37
javascript/ql/test/library-tests/frameworks/Vue/router.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import Router from 'vue-router';
|
||||
|
||||
export const router = new Router({
|
||||
routes: [
|
||||
{
|
||||
path: '/foo',
|
||||
beforeEnter: (to, from, next) => {
|
||||
to.query.x;
|
||||
from.query.x;
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/bar',
|
||||
beforeEnter: (to, from, next) => {
|
||||
to.query.x;
|
||||
from.query.x;
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
to.query.x;
|
||||
from.query.x;
|
||||
}
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
to.query.x;
|
||||
from.query.x;
|
||||
});
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
to.query.x;
|
||||
from.query.x;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
instance_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 |
|
||||
@@ -27,6 +29,7 @@ instance_getAPropertyValue
|
||||
| tst.js:94:2:96:3 | new Vue ... f,\\n\\t}) | dataA | tst.js:89:22:89:23 | 42 |
|
||||
| tst.js:99:2:104:3 | new Vue ... \\t\\t}\\n\\t}) | dataA | tst.js:100:18:100:19 | 42 |
|
||||
instance_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) => { } |
|
||||
@@ -56,6 +59,7 @@ instance_getOption
|
||||
| tst.js:99:2:104:3 | new Vue ... \\t\\t}\\n\\t}) | data | tst.js:100:9:100:21 | { dataA: 42 } |
|
||||
| tst.js:99:2:104:3 | new Vue ... \\t\\t}\\n\\t}) | methods | tst.js:101:12:103:3 | {\\n\\t\\t\\tm: ... ; }\\n\\t\\t} |
|
||||
instance
|
||||
| compont-with-route.vue:0:0:0:0 | compont-with-route.vue |
|
||||
| single-component-file-1.vue:0:0:0:0 | single-component-file-1.vue |
|
||||
| single-file-component-2.vue:0:0:0:0 | single-file-component-2.vue |
|
||||
| single-file-component-3.vue:0:0:0:0 | single-file-component-3.vue |
|
||||
@@ -80,6 +84,10 @@ instance_heapStep
|
||||
| tst.js:102:20:102:29 | this.dataA | tst.js:100:18:100:19 | 42 | tst.js:102:20:102:29 | this.dataA |
|
||||
| tst.js:102:20:102:29 | this.dataA | tst.js:102:20:102:23 | this | tst.js:102:20:102:29 | this.dataA |
|
||||
templateElement
|
||||
| compont-with-route.vue:1:1:3:11 | <template>...</> |
|
||||
| compont-with-route.vue:2:5:51:9 | <p>...</> |
|
||||
| compont-with-route.vue:4:1:49:9 | <script>...</> |
|
||||
| compont-with-route.vue:50:1:51:8 | <style>...</> |
|
||||
| single-component-file-1.vue:1:1:3:11 | <template>...</> |
|
||||
| single-component-file-1.vue:2:5:10:8 | <p>...</> |
|
||||
| single-component-file-1.vue:4:1:8:9 | <script>...</> |
|
||||
@@ -101,11 +109,14 @@ templateElement
|
||||
| single-file-component-5.vue:4:1:16:9 | <script>...</> |
|
||||
| single-file-component-5.vue:17:1:18:8 | <style>...</> |
|
||||
vhtmlSourceWrite
|
||||
| compont-with-route.vue:31:14:31:34 | this.$r ... ery.foo | compont-with-route.vue:31:14:31:30 | this.$route.query | compont-with-route.vue:31:14:31:34 | this.$r ... ery.foo |
|
||||
| compont-with-route.vue:31:14:31:34 | this.$r ... ery.foo | 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: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-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: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:13:14:13:15 | 42 | single-file-component-5.vue:2:8:2:21 | v-html=dataA |
|
||||
xssSink
|
||||
| compont-with-route.vue:2:8:2:21 | v-html=dataA |
|
||||
| single-component-file-1.vue:2:8:2:21 | v-html=dataA |
|
||||
| single-file-component-2.vue:2:8:2:21 | v-html=dataA |
|
||||
| single-file-component-3.vue:2:8:2:21 | v-html=dataA |
|
||||
@@ -113,3 +124,29 @@ xssSink
|
||||
| single-file-component-5.vue:2:8:2:21 | v-html=dataA |
|
||||
| tst.js:5:13:5:13 | a |
|
||||
| tst.js:38:12:38:17 | danger |
|
||||
remoteFlowSource
|
||||
| compont-with-route.vue:13:17:13:21 | newId |
|
||||
| compont-with-route.vue:13:24:13:28 | oldId |
|
||||
| compont-with-route.vue:22:7:22:24 | this.$route.params |
|
||||
| compont-with-route.vue:23:7:23:23 | this.$route.query |
|
||||
| compont-with-route.vue:24:7:24:22 | this.$route.hash |
|
||||
| compont-with-route.vue:25:7:25:22 | this.$route.path |
|
||||
| compont-with-route.vue:26:7:26:26 | this.$route.fullPath |
|
||||
| compont-with-route.vue:27:7:27:31 | router. ... e.query |
|
||||
| compont-with-route.vue:31:14:31:30 | this.$route.query |
|
||||
| compont-with-route.vue:35:7:35:14 | to.query |
|
||||
| compont-with-route.vue:36:7:36:16 | from.query |
|
||||
| compont-with-route.vue:40:7:40:14 | to.query |
|
||||
| compont-with-route.vue:41:7:41:16 | from.query |
|
||||
| compont-with-route.vue:45:7:45:14 | to.query |
|
||||
| compont-with-route.vue:46:7:46:16 | from.query |
|
||||
| router.js:8:17:8:24 | to.query |
|
||||
| router.js:9:17:9:26 | from.query |
|
||||
| router.js:15:25:15:32 | to.query |
|
||||
| router.js:16:25:16:34 | from.query |
|
||||
| router.js:23:9:23:16 | to.query |
|
||||
| router.js:24:9:24:18 | from.query |
|
||||
| router.js:29:5:29:12 | to.query |
|
||||
| router.js:30:5:30:14 | from.query |
|
||||
| router.js:34:5:34:12 | to.query |
|
||||
| router.js:35:5:35:14 | from.query |
|
||||
|
||||
@@ -29,3 +29,5 @@ query predicate vhtmlSourceWrite(
|
||||
import semmle.javascript.security.dataflow.DomBasedXss
|
||||
|
||||
query predicate xssSink(DomBasedXss::Sink s) { any() }
|
||||
|
||||
query RemoteFlowSource remoteFlowSource() { any() }
|
||||
|
||||
Reference in New Issue
Block a user