Skip to content

Commit f43a950

Browse files
committed
feat: rethink the whole plugin system
1 parent ed934fd commit f43a950

File tree

5 files changed

+97
-13
lines changed

5 files changed

+97
-13
lines changed

docs/README.md

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,76 @@ Isolation was done using the [Secure ECMAScript](https://github.com/endojs/endo)
3737

3838
Interactive Live2Ds were taken from [Z_DK's Steam Workshop](https://steamcommunity.com/id/xingsuileixi/myworkshopfiles/?appid=431960)
3939

40-
One more thing: we need to take care of DOM script tags that plugins can add when have the DOM access. Otherwise, this sandbox can be escaped. Maybe we can overwrite the `document.createElement` before freezing it? And just to be sure, listen for `head` element changes for possible script tag additions?
40+
<details>
41+
We need to take care of DOM script tags that plugins can add when have the DOM access. Otherwise, this sandbox can be escaped. Maybe we can overwrite the `document.createElement` before freezing it? And just to be sure, listen for `head` element changes for possible script tag additions?
42+
43+
Update: Seems like giving DOM access to the compartment basically ruins the whole purpose of plugin isolation, since one can add a JS code to any DOM element that will be executed in the global scope.
44+
45+
Well, let's make these permissions then?
46+
47+
- Full DOM access
48+
- File System access (window.__TAURI__.fs)
49+
- System shell access (window.__TAURI__.shell)
50+
- ...other Tauri-specific plugins access
51+
52+
How to adapt to these rules? Do I really need compartments here? Isn't it better to just use `new Function` at this point? What to do about script tags and the above DOM element JS executions? Should I expose Tauri API through `window` object? How to import and share Tauri API then?
53+
54+
---
55+
56+
After some thoughts, I decided to go with this approach:
57+
58+
Tauri API **will not** be exposed through `window`
59+
60+
Account sign in is made through another webview window with no plugin access to prevent any possible security vulnerabilities.
61+
62+
- Kaede User Repository (KUR) - plugins that are free to publish for anyone. Will use Secure ECMAScript Compartments for not full DOM access, otherwise they are executed with `new Function`
63+
- Moderated plugins - every plugin publish/update goes through my checks. I only need plugin's source code and build manual. Executed with `new Function` or `Module Federation Runtime API`, have full DOM access by default, only require Tauri API scope permissions (fs, shell, global shortcuts, network, os info, etc.) from user
64+
65+
Permissions:
66+
67+
Runs in compartment:
68+
69+
- CSS: Apply CSS stylesheet - Custom
70+
- | Edit/replace element class list (by id)
71+
- | Edit element styles (by id)
72+
73+
Runs in `new Function` or Module Federation Runtime API (`eval`):
74+
75+
Note: I need to generalize these, because seeing 30 permissions that vary from doing 1 simple ass thing (tauri-core-app) to doing 50+ dangerous things (tauri-plugin-fs/tauri-plugin-shell) is not ok
76+
77+
- Website capabilities: everything that HTML & JS & CSS offers (no access to Tauri API)
78+
- Tauri Core: Allow to change application theme (not the UI) - App
79+
- | Emit/listen events to/from the backend - Event
80+
- | Create images from paths - Image
81+
- | Menu
82+
- | Path
83+
- | Resources
84+
- | Tray
85+
- | WebViews & windows management
86+
- Tauri Plugins: Clipboard writing (texts, images) - Clipboard Manager
87+
- | Clipboard reading (texts)
88+
- | Clipboard reading (images)
89+
- |
90+
- plugin-drpc (i will format these a bit later)
91+
- plugin-fs
92+
- plugin-global-shortcut
93+
- plugin-http
94+
- plugin-log
95+
- plugin-notification
96+
- plugin-opener
97+
- plugin-os
98+
- plugin-process
99+
- plugin-shell
100+
- plugin-upload
101+
102+
not related, but need to store it somewhere:
103+
104+
context menu has 50000 z-index
105+
log menu has 40000 z-index
106+
sidebar has 3000 z-index
107+
</details>
108+
109+
If there is no video, [click here](https://github.com/user-attachments/assets/a1ccc9f2-0244-437b-8883-a68a26953e2a)
41110

42111
<video src="https://github.com/user-attachments/assets/a1ccc9f2-0244-437b-8883-a68a26953e2a" width="320" height="240" controls></video>
43112

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<link rel="stylesheet" href="./assets/index.b5021dcf.css" />
88
<title>Kaede</title>
99
</head>
10-
<body>
10+
<body style="background:#111111">
1111
<div id="app"></div>
1212
<div id="blue_archive" style="position:absolute;left:0;top:0;right:0;bottom:0"></div>
1313
<script type="module" src="/src/main.ts"></script>

src/App.vue

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,34 @@ const LogViewer = defineAsyncComponent(
5959
6060
try {
6161
console.log("Action: Trying to lockdown every JS globals");
62-
lockdown({
63-
"evalTaming": "unsafeEval",
64-
});
62+
// lockdown({
63+
// "evalTaming": "unsafeEval",
64+
// });
6565
} catch {
6666
console.log("Already locked down");
6767
}
6868
6969
window.Buffer = Buffer;
7070
71-
console.log(window);
71+
const _temporary: object = {};
72+
73+
for (const _name of Object.getOwnPropertyNames(window)) {
74+
if (["Infinity", "undefined", "NaN"].includes(_name)) {
75+
continue;
76+
}
77+
78+
if (_name[0] === _name[0].toLowerCase() && typeof window[_name] === "function") {
79+
_temporary[_name] = window[_name].bind(window);
80+
81+
continue;
82+
}
83+
84+
_temporary[_name] = window[_name];
85+
}
7286
7387
console.log("Action: Creating a safe compartment");
7488
const c = new Compartment({
75-
"globals": {
89+
"globals": _temporary, /* {
7690
...window,
7791
"console": {
7892
...console,
@@ -157,14 +171,15 @@ const LogViewer = defineAsyncComponent(
157171
clearInterval: window.clearInterval.bind(window),
158172
clearTimeout: window.clearTimeout.bind(window),
159173
FontFace: window.FontFace,
160-
},
174+
}, */
161175
"__options__": true,
162176
});
163177
const t2 = performance.now();
164178
165179
console.log("Action: Running the contained code");
166180
try {
167-
await c.evaluate(_cleaned);
181+
// await c.evaluate(_cleaned);
182+
// eval(_cleaned);
168183
} catch (error) {
169184
console.error("Error in the container:", error);
170185
}

src/components/layout/Sidebar.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ const globalStates = inject<ContextGlobalStatesType>(GlobalStatesContextKey);
1111

1212
<template>
1313
<div class="h-full w-20"></div>
14-
<TransitionGroup name="fade" tag="div" class="absolute left-0 top-0 h-vh w-20 flex flex-col items-center bg-[theme(colors.neutral.950/.3)] z-1000 backdrop-blur-sm">
14+
<TransitionGroup name="fade" tag="div" class="absolute left-0 top-0 h-vh w-20 flex flex-col items-center bg-neutral-950 z-10000">
1515
<button
1616
v-for="item in globalStates?.sidebarItems"
1717
:key="item.path"
1818
:disabled="item.path === globalStates?.page"
1919
@mousedown="item.action"
2020
@touchstart="item.action"
2121
@click="item.action"
22-
class="relative size-20 flex shrink-0 flex-col items-center justify-center gap-1 text-white transition-[background-color] duration-150 disabled:bg-[theme(colors.neutral.100/.15)]"
22+
class="relative size-20 flex shrink-0 flex-col items-center justify-center gap-1 text-white transition-[background-color] duration-150 disabled:bg-neutral-900"
2323
name="sidebar__item"
2424
>
2525
<span

src/components/logging/LogViewer.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,12 @@ onMounted(async () => {
113113
<template>
114114
<div
115115
@contextmenu.prevent
116-
class="absolute bottom-0 left-0 right-0 top-0 grid place-items-center pointer-events-none"
116+
class="absolute z-40000 bottom-0 left-0 right-0 top-0 grid place-items-center pointer-events-none"
117117
>
118118
<div
119119
@contextmenu.prevent
120120
@contextmenu="showContextMenu"
121-
class="rounded-md pointer-events-auto z-40000 h-fit max-h-[calc(100vh-64px)] max-w-[calc(100vw-64px)] w-fit flex flex-col gap-2 bg-neutral-900 p-4 text-white drop-shadow-lg"
121+
class="rounded-md pointer-events-auto h-fit max-h-[calc(100vh-64px)] max-w-[calc(100vw-64px)] w-fit flex flex-col gap-2 bg-neutral-900 p-4 text-white drop-shadow-lg"
122122
>
123123
<div class="w-full flex flex-nowrap items-start justify-between gap-4 pb-2 shrink-0">
124124
<div class="flex flex-col gap-2">

0 commit comments

Comments
 (0)