Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions change-notes/1.26/analysis-javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
- [styled-components](https://www.npmjs.com/package/styled-components)
- [throttle-debounce](https://www.npmjs.com/package/throttle-debounce)
- [underscore](https://www.npmjs.com/package/underscore)
- [vue-router](https://www.npmjs.com/package/vue-router)

* Analyzing files with the ".cjs" extension is now supported.
* ES2021 features are now supported.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* The query parameter of Vue Router's `useRoute()` Composition API is now recognized as a client-side remote flow source.
* Added flow models for Vue's `ref`, `shallowRef`, `toRef`, `reactive`, and `computed` Composition API helpers.
9 changes: 9 additions & 0 deletions javascript/ql/lib/ext/vue.model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: summaryModel
data:
# `computed(() => ...)` — function overload: the getter's return value flows to `.value`.
- ["vue", "Member[computed]", "Argument[0].ReturnValue", "ReturnValue.Member[value]", "value"]
# `computed({ get() { ... } })` — object overload: the `get` getter's return value flows to `.value`.
- ["vue", "Member[computed]", "Argument[0].Member[get].ReturnValue", "ReturnValue.Member[value]", "value"]
36 changes: 36 additions & 0 deletions javascript/ql/lib/semmle/javascript/frameworks/Vue.qll
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,40 @@ module Vue {
result = any(GlobalVueEntryPoint e).getANode()
}

/**
* Models data flow through Vue Composition API helpers.
*
* Note that `computed` is not modeled here but in the `vue.model.yml` data
* extension, because its object overload (`computed({ get() { ... } })`)
* requires callback flow synthesis that data extensions support but a
* hand-written `SummarizedCallable` does not.
*/
overlay[local?]
private class VueCompositionApiSummary extends DataFlow::SummarizedCallable::Range {
string name;

VueCompositionApiSummary() {
name = ["ref", "shallowRef", "toRef", "reactive"] and
this = "vue." + name
}

override predicate propagatesFlow(string input, string output, boolean preservesValue) {
name = ["ref", "shallowRef", "toRef"] and
input = "Argument[0]" and
output = "ReturnValue.Member[value]" and
preservesValue = true
or
name = "reactive" and
input = "Argument[0]" and
output = "ReturnValue" and
preservesValue = false
}

override DataFlow::InvokeNode getACall() {
result = API::moduleImport("vue").getMember(name).getACall()
}
}

/**
* Gets a reference to the 'Vue' object.
*/
Expand Down Expand Up @@ -652,6 +686,8 @@ module Vue {
t.start() and
(
exists(API::Node router | router = API::moduleImport("vue-router") |
result = router.getMember("useRoute").getACall()
or
result = router.getInstance().getMember("currentRoute").asSource()
or
result =
Expand Down
4 changes: 3 additions & 1 deletion javascript/ql/test/library-tests/frameworks/Vue/router.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Router from 'vue-router';
import Router, { useRoute } from 'vue-router';

export const router = new Router({
routes: [
Expand Down Expand Up @@ -43,3 +43,5 @@ router.afterEach((to, from) => {
to.query.x;
from.query.x;
});

useRoute().query;
15 changes: 15 additions & 0 deletions javascript/ql/test/library-tests/frameworks/Vue/tests.expected
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ remoteFlowSource
| router.js:39:5:39:14 | from.query |
| router.js:43:5:43:12 | to.query |
| router.js:44:5:44:14 | from.query |
| router.js:47:1:47:16 | useRoute().query |
parseErrors
attribute
| compont-with-route.vue:2:8:2:21 | v-html=dataA | v-html |
Expand Down Expand Up @@ -239,10 +240,24 @@ threatModelSource
| router.js:39:5:39:14 | from.query | remote |
| router.js:43:5:43:12 | to.query | remote |
| router.js:44:5:44:14 | from.query | remote |
| router.js:47:1:47:16 | useRoute().query | remote |
| single-component-file-1.vue:7:45:7:54 | this.input | view-component-input |
| single-file-component-3-script.js:5:42:5:51 | this.input | view-component-input |
| single-file-component-4.vue:21:14:21:23 | this.input | view-component-input |
| single-file-component-5.vue:19:14:19:23 | this.input | view-component-input |
| single-file-component-6.vue:5:11:5:15 | input | view-component-input |
| single-file-component-7.vue:5:11:5:15 | input | view-component-input |
| single-file-component-8.vue:5:11:5:15 | input | view-component-input |
compositionApiDataFlow
| tst.js:119:14:119:26 | source("ref") | tst.js:119:6:119:33 | Vue.ref ... ).value |
| tst.js:120:21:120:40 | source("shallowRef") | tst.js:120:6:120:47 | Vue.sha ... ).value |
| tst.js:121:16:121:30 | source("toRef") | tst.js:121:6:121:37 | Vue.toR ... ).value |
| tst.js:123:25:123:42 | source("computed") | tst.js:123:6:123:49 | Vue.com ... ).value |
| tst.js:124:36:124:59 | source( ... bject") | tst.js:124:6:124:82 | Vue.com ... ).value |
compositionApiTaintFlow
| tst.js:119:14:119:26 | source("ref") | tst.js:119:6:119:33 | Vue.ref ... ).value |
| tst.js:120:21:120:40 | source("shallowRef") | tst.js:120:6:120:47 | Vue.sha ... ).value |
| tst.js:121:16:121:30 | source("toRef") | tst.js:121:6:121:37 | Vue.toR ... ).value |
| tst.js:122:19:122:36 | source("reactive") | tst.js:122:6:122:37 | Vue.rea ... tive")) |
| tst.js:123:25:123:42 | source("computed") | tst.js:123:6:123:49 | Vue.com ... ).value |
| tst.js:124:36:124:59 | source( ... bject") | tst.js:124:6:124:82 | Vue.com ... ).value |
18 changes: 18 additions & 0 deletions javascript/ql/test/library-tests/frameworks/Vue/tests.ql
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
import javascript
import semmle.javascript.security.dataflow.DomBasedXssCustomizations

module TestConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.(DataFlow::CallNode).getCalleeName() = "source"
}

predicate isSink(DataFlow::Node sink) {
sink = any(DataFlow::CallNode call | call.getCalleeName() = "sink").getAnArgument()
}
}

module TestDataFlow = DataFlow::Global<TestConfig>;

module TestTaintFlow = TaintTracking::Global<TestConfig>;

query predicate compositionApiDataFlow = TestDataFlow::flow/2;

query predicate compositionApiTaintFlow = TestTaintFlow::flow/2;

query predicate component_getAPropertyValue(Vue::Component c, string name, DataFlow::Node prop) {
c.getAPropertyValue(name) = prop
}
Expand Down
7 changes: 7 additions & 0 deletions javascript/ql/test/library-tests/frameworks/Vue/tst.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,10 @@ let subclass2 = base.extend({
fromSubclass2: 100
}
});

sink(Vue.ref(source("ref")).value);
sink(Vue.shallowRef(source("shallowRef")).value);
sink(Vue.toRef(source("toRef")).value);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot implement this fix

sink(Vue.reactive(source("reactive")));
sink(Vue.computed(() => source("computed")).value);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot implement this fix

sink(Vue.computed({ get() { return source("computedObject"); }, set(v) {} }).value);
Loading