summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
authorSteven Guerrero <stephenguerrero43@gmail.com>2021-01-06 15:31:16 -0500
committerGitHub <noreply@github.com>2021-01-06 21:31:16 +0100
commitadc2f08c178f51b3ddd5f1c2e3d7f5603424521e (patch)
tree99d61fca9bfdad4e427f9d2d6d1719f69d09c96a /cli
parent2e18fcebcc2ee931ee952ac2fe2175d6ec7acf69 (diff)
feat: Add configurable permissions for Workers (#8215)
This commit adds new option to "Worker" Web API that allows to configure permissions. New "Worker.deno.permissions" option can be used to define limited permissions to the worker thread by either: - inherit set of parent thread permissions - use limited subset of parent thread permissions - revoke all permissions (full sandbox) In order to achieve this functionality "CliModuleLoader" was modified to accept "initial permissions", which are used for top module loading (ie. uses parent thread permission set to load top level module of a worker).
Diffstat (limited to 'cli')
-rw-r--r--cli/dts/lib.deno.shared_globals.d.ts56
-rw-r--r--cli/main.rs5
-rw-r--r--cli/module_loader.rs22
-rw-r--r--cli/tests/unstable_worker.ts6
-rw-r--r--cli/tests/workers/bench_worker.ts (renamed from cli/tests/subdir/bench_worker.ts)0
-rw-r--r--cli/tests/workers/busy_worker.js (renamed from cli/tests/subdir/busy_worker.js)0
-rw-r--r--cli/tests/workers/deno_worker.ts (renamed from cli/tests/subdir/deno_worker.ts)0
-rw-r--r--cli/tests/workers/event_worker.js (renamed from cli/tests/subdir/event_worker.js)0
-rw-r--r--cli/tests/workers/event_worker_scope.js (renamed from cli/tests/subdir/event_worker_scope.js)0
-rw-r--r--cli/tests/workers/fetching_worker.js (renamed from cli/tests/subdir/fetching_worker.js)2
-rw-r--r--cli/tests/workers/immediately_close_worker.js (renamed from cli/tests/immediately_close_worker.js)0
-rw-r--r--cli/tests/workers/nested_worker.js (renamed from cli/tests/subdir/nested_worker.js)0
-rw-r--r--cli/tests/workers/no_permissions_worker.js17
-rw-r--r--cli/tests/workers/non_deno_worker.js (renamed from cli/tests/subdir/non_deno_worker.js)0
-rw-r--r--cli/tests/workers/parent_read_check_granular_worker.js43
-rw-r--r--cli/tests/workers/parent_read_check_worker.js27
-rw-r--r--cli/tests/workers/racy_worker.js (renamed from cli/tests/subdir/racy_worker.js)0
-rw-r--r--cli/tests/workers/read_check_granular_worker.js13
-rw-r--r--cli/tests/workers/read_check_worker.js7
-rw-r--r--cli/tests/workers/sibling_worker.js (renamed from cli/tests/subdir/sibling_worker.js)0
-rw-r--r--cli/tests/workers/test_worker.js (renamed from cli/tests/subdir/test_worker.js)0
-rw-r--r--cli/tests/workers/test_worker.ts (renamed from cli/tests/subdir/test_worker.ts)0
-rw-r--r--cli/tests/workers/throwing_worker.js (renamed from cli/tests/subdir/throwing_worker.js)0
-rw-r--r--cli/tests/workers/worker_crypto.js (renamed from cli/tests/subdir/worker_crypto.js)0
-rw-r--r--cli/tests/workers/worker_globals.ts (renamed from cli/tests/subdir/worker_globals.ts)0
-rw-r--r--cli/tests/workers/worker_unstable.ts (renamed from cli/tests/subdir/worker_unstable.ts)0
-rw-r--r--cli/tests/workers_round_robin_bench.ts2
-rw-r--r--cli/tests/workers_startup_bench.ts2
-rw-r--r--cli/tests/workers_test.ts280
29 files changed, 440 insertions, 42 deletions
diff --git a/cli/dts/lib.deno.shared_globals.d.ts b/cli/dts/lib.deno.shared_globals.d.ts
index e4d763ec0..74abbc95b 100644
--- a/cli/dts/lib.deno.shared_globals.d.ts
+++ b/cli/dts/lib.deno.shared_globals.d.ts
@@ -662,24 +662,33 @@ declare class Worker extends EventTarget {
options?: {
type?: "classic" | "module";
name?: string;
- /** UNSTABLE: New API. Expect many changes; most likely this
- * field will be made into an object for more granular
- * configuration of worker thread (permissions, import map, etc.).
+ /** UNSTABLE: New API.
*
- * Set to `true` to make `Deno` namespace and all of its methods
- * available to worker thread.
- *
- * Currently worker inherits permissions from main thread (permissions
- * given using `--allow-*` flags).
- * Configurable permissions are on the roadmap to be implemented.
+ * Set deno.namespace to `true` to make `Deno` namespace and all of its methods
+ * available to worker thread. The namespace is disabled by default.
+ *
+ * Configure deno.permissions options to change the level of access the worker will
+ * have. By default it will inherit the permissions of its parent thread. The permissions
+ * of a worker can't be extended beyond its parent's permissions reach.
+ * - "inherit" will take the permissions of the thread the worker is created in
+ * - You can disable/enable permissions all together by passing a boolean
+ * - You can provide a list of routes relative to the file the worker
+ * is created in to limit the access of the worker (read/write permissions only)
*
* Example:
*
* ```ts
* // mod.ts
* const worker = new Worker(
- * new URL("deno_worker.ts", import.meta.url).href,
- * { type: "module", deno: true }
+ * new URL("deno_worker.ts", import.meta.url).href, {
+ * type: "module",
+ * deno: {
+ * namespace: true,
+ * permissions: {
+ * read: true,
+ * },
+ * },
+ * }
* );
* worker.postMessage({ cmd: "readFile", fileName: "./log.txt" });
*
@@ -707,7 +716,30 @@ declare class Worker extends EventTarget {
* hello world2
*
*/
- deno?: boolean;
+ // TODO(Soremwar)
+ // `deno: true` is kept for backwards compatibility with the previous worker
+ // options implementation. Remove for 2.0
+ deno?: true | {
+ namespace?: boolean;
+ /** Set to false to disable all the permissions in the worker */
+ permissions?: "inherit" | false | {
+ env?: "inherit" | boolean;
+ hrtime?: "inherit" | boolean;
+ /**
+ * The format of the net access list must be `hostname[:port]`
+ * in order to be resolved
+ *
+ * ```
+ * net: ["https://deno.land", "localhost:8080"],
+ * ```
+ * */
+ net?: "inherit" | boolean | string[];
+ plugin?: "inherit" | boolean;
+ read?: "inherit" | boolean | Array<string | URL>;
+ run?: "inherit" | boolean;
+ write?: "inherit" | boolean | Array<string | URL>;
+ };
+ };
},
);
postMessage(message: any, transfer: ArrayBuffer[]): void;
diff --git a/cli/main.rs b/cli/main.rs
index b622cccf3..a247389d9 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -100,7 +100,10 @@ fn create_web_worker_callback(
|| program_state.coverage_dir.is_some();
let maybe_inspector_server = program_state.maybe_inspector_server.clone();
- let module_loader = CliModuleLoader::new_for_worker(program_state.clone());
+ let module_loader = CliModuleLoader::new_for_worker(
+ program_state.clone(),
+ args.parent_permissions.clone(),
+ );
let create_web_worker_cb =
create_web_worker_callback(program_state.clone());
diff --git a/cli/module_loader.rs b/cli/module_loader.rs
index aab951c4a..c91a2336b 100644
--- a/cli/module_loader.rs
+++ b/cli/module_loader.rs
@@ -22,6 +22,10 @@ pub struct CliModuleLoader {
/// import map file will be resolved and set.
pub import_map: Option<ImportMap>,
pub lib: TypeLib,
+ /// The initial set of permissions used to resolve the imports in the worker.
+ /// They are decoupled from the worker permissions since read access errors
+ /// must be raised based on the parent thread permissions
+ pub initial_permissions: Rc<RefCell<Option<Permissions>>>,
pub program_state: Arc<ProgramState>,
}
@@ -38,11 +42,15 @@ impl CliModuleLoader {
Rc::new(CliModuleLoader {
import_map,
lib,
+ initial_permissions: Rc::new(RefCell::new(None)),
program_state,
})
}
- pub fn new_for_worker(program_state: Arc<ProgramState>) -> Rc<Self> {
+ pub fn new_for_worker(
+ program_state: Arc<ProgramState>,
+ permissions: Permissions,
+ ) -> Rc<Self> {
let lib = if program_state.flags.unstable {
TypeLib::UnstableDenoWorker
} else {
@@ -52,6 +60,7 @@ impl CliModuleLoader {
Rc::new(CliModuleLoader {
import_map: None,
lib,
+ initial_permissions: Rc::new(RefCell::new(Some(permissions))),
program_state,
})
}
@@ -118,7 +127,16 @@ impl ModuleLoader for CliModuleLoader {
let state = op_state.borrow();
// The permissions that should be applied to any dynamically imported module
- let dynamic_permissions = state.borrow::<Permissions>().clone();
+ let dynamic_permissions =
+ // If there are initial permissions assigned to the loader take them
+ // and use only once for top level module load.
+ // Otherwise use permissions assigned to the current worker.
+ if let Some(permissions) = self.initial_permissions.borrow_mut().take() {
+ permissions
+ } else {
+ state.borrow::<Permissions>().clone()
+ };
+
let lib = self.lib.clone();
drop(state);
diff --git a/cli/tests/unstable_worker.ts b/cli/tests/unstable_worker.ts
index 6b5304edf..429754dfe 100644
--- a/cli/tests/unstable_worker.ts
+++ b/cli/tests/unstable_worker.ts
@@ -1,8 +1,10 @@
const w = new Worker(
- new URL("subdir/worker_unstable.ts", import.meta.url).href,
+ new URL("workers/worker_unstable.ts", import.meta.url).href,
{
type: "module",
- deno: true,
+ deno: {
+ namespace: true,
+ },
name: "Unstable Worker",
},
);
diff --git a/cli/tests/subdir/bench_worker.ts b/cli/tests/workers/bench_worker.ts
index 7e85eed03..7e85eed03 100644
--- a/cli/tests/subdir/bench_worker.ts
+++ b/cli/tests/workers/bench_worker.ts
diff --git a/cli/tests/subdir/busy_worker.js b/cli/tests/workers/busy_worker.js
index 7deba0321..7deba0321 100644
--- a/cli/tests/subdir/busy_worker.js
+++ b/cli/tests/workers/busy_worker.js
diff --git a/cli/tests/subdir/deno_worker.ts b/cli/tests/workers/deno_worker.ts
index 6a57c47f0..6a57c47f0 100644
--- a/cli/tests/subdir/deno_worker.ts
+++ b/cli/tests/workers/deno_worker.ts
diff --git a/cli/tests/subdir/event_worker.js b/cli/tests/workers/event_worker.js
index 849b6026c..849b6026c 100644
--- a/cli/tests/subdir/event_worker.js
+++ b/cli/tests/workers/event_worker.js
diff --git a/cli/tests/subdir/event_worker_scope.js b/cli/tests/workers/event_worker_scope.js
index 0381801a8..0381801a8 100644
--- a/cli/tests/subdir/event_worker_scope.js
+++ b/cli/tests/workers/event_worker_scope.js
diff --git a/cli/tests/subdir/fetching_worker.js b/cli/tests/workers/fetching_worker.js
index 3e33d1c9e..e1bcdf911 100644
--- a/cli/tests/subdir/fetching_worker.js
+++ b/cli/tests/workers/fetching_worker.js
@@ -1,5 +1,5 @@
const r = await fetch(
- "http://localhost:4545/cli/tests/subdir/fetching_worker.js",
+ "http://localhost:4545/cli/tests/workers/fetching_worker.js",
);
await r.text();
postMessage("Done!");
diff --git a/cli/tests/immediately_close_worker.js b/cli/tests/workers/immediately_close_worker.js
index 8fd27343a..8fd27343a 100644
--- a/cli/tests/immediately_close_worker.js
+++ b/cli/tests/workers/immediately_close_worker.js
diff --git a/cli/tests/subdir/nested_worker.js b/cli/tests/workers/nested_worker.js
index 4b51b8763..4b51b8763 100644
--- a/cli/tests/subdir/nested_worker.js
+++ b/cli/tests/workers/nested_worker.js
diff --git a/cli/tests/workers/no_permissions_worker.js b/cli/tests/workers/no_permissions_worker.js
new file mode 100644
index 000000000..8a4f79d57
--- /dev/null
+++ b/cli/tests/workers/no_permissions_worker.js
@@ -0,0 +1,17 @@
+self.onmessage = async () => {
+ const hrtime = await Deno.permissions.query({ name: "hrtime" });
+ const net = await Deno.permissions.query({ name: "net" });
+ const plugin = await Deno.permissions.query({ name: "plugin" });
+ const read = await Deno.permissions.query({ name: "read" });
+ const run = await Deno.permissions.query({ name: "run" });
+ const write = await Deno.permissions.query({ name: "write" });
+ self.postMessage(
+ hrtime.state === "denied" &&
+ net.state === "denied" &&
+ plugin.state === "denied" &&
+ read.state === "denied" &&
+ run.state === "denied" &&
+ write.state === "denied",
+ );
+ self.close();
+};
diff --git a/cli/tests/subdir/non_deno_worker.js b/cli/tests/workers/non_deno_worker.js
index 773721560..773721560 100644
--- a/cli/tests/subdir/non_deno_worker.js
+++ b/cli/tests/workers/non_deno_worker.js
diff --git a/cli/tests/workers/parent_read_check_granular_worker.js b/cli/tests/workers/parent_read_check_granular_worker.js
new file mode 100644
index 000000000..1a7182e17
--- /dev/null
+++ b/cli/tests/workers/parent_read_check_granular_worker.js
@@ -0,0 +1,43 @@
+import { fromFileUrl } from "../../../std/path/mod.ts";
+
+const worker = new Worker(
+ new URL("./read_check_granular_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ read: [],
+ },
+ },
+ },
+);
+
+let received = 0;
+const messages = [];
+
+worker.onmessage = ({ data: childResponse }) => {
+ received++;
+ postMessage({
+ childHasPermission: childResponse.hasPermission,
+ index: childResponse.index,
+ parentHasPermission: messages[childResponse.index],
+ });
+ if (received === messages.length) {
+ worker.terminate();
+ }
+};
+
+onmessage = async ({ data }) => {
+ const { state } = await Deno.permissions.query({
+ name: "read",
+ path: fromFileUrl(new URL(data.route, import.meta.url)),
+ });
+
+ messages[data.index] = state === "granted";
+
+ worker.postMessage({
+ index: data.index,
+ route: data.route,
+ });
+};
diff --git a/cli/tests/workers/parent_read_check_worker.js b/cli/tests/workers/parent_read_check_worker.js
new file mode 100644
index 000000000..ec92cca3f
--- /dev/null
+++ b/cli/tests/workers/parent_read_check_worker.js
@@ -0,0 +1,27 @@
+onmessage = async () => {
+ const { state } = await Deno.permissions.query({
+ name: "read",
+ });
+
+ const worker = new Worker(
+ new URL("./read_check_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ read: false,
+ },
+ },
+ },
+ );
+
+ worker.onmessage = ({ data: childHasPermission }) => {
+ postMessage({
+ parentHasPermission: state === "granted",
+ childHasPermission,
+ });
+ close();
+ };
+ worker.postMessage(null);
+};
diff --git a/cli/tests/subdir/racy_worker.js b/cli/tests/workers/racy_worker.js
index 83756b791..83756b791 100644
--- a/cli/tests/subdir/racy_worker.js
+++ b/cli/tests/workers/racy_worker.js
diff --git a/cli/tests/workers/read_check_granular_worker.js b/cli/tests/workers/read_check_granular_worker.js
new file mode 100644
index 000000000..4eddb7a75
--- /dev/null
+++ b/cli/tests/workers/read_check_granular_worker.js
@@ -0,0 +1,13 @@
+import { fromFileUrl } from "../../../std/path/mod.ts";
+
+onmessage = async ({ data }) => {
+ const { state } = await Deno.permissions.query({
+ name: "read",
+ path: fromFileUrl(new URL(data.route, import.meta.url)),
+ });
+
+ postMessage({
+ hasPermission: state === "granted",
+ index: data.index,
+ });
+};
diff --git a/cli/tests/workers/read_check_worker.js b/cli/tests/workers/read_check_worker.js
new file mode 100644
index 000000000..2ad01bf5b
--- /dev/null
+++ b/cli/tests/workers/read_check_worker.js
@@ -0,0 +1,7 @@
+onmessage = async () => {
+ const { state } = await Deno.permissions.query({
+ name: "read",
+ });
+ postMessage(state === "granted");
+ close();
+};
diff --git a/cli/tests/subdir/sibling_worker.js b/cli/tests/workers/sibling_worker.js
index 99707e5d6..99707e5d6 100644
--- a/cli/tests/subdir/sibling_worker.js
+++ b/cli/tests/workers/sibling_worker.js
diff --git a/cli/tests/subdir/test_worker.js b/cli/tests/workers/test_worker.js
index 4260975a6..4260975a6 100644
--- a/cli/tests/subdir/test_worker.js
+++ b/cli/tests/workers/test_worker.js
diff --git a/cli/tests/subdir/test_worker.ts b/cli/tests/workers/test_worker.ts
index ca79dcfe4..ca79dcfe4 100644
--- a/cli/tests/subdir/test_worker.ts
+++ b/cli/tests/workers/test_worker.ts
diff --git a/cli/tests/subdir/throwing_worker.js b/cli/tests/workers/throwing_worker.js
index 56ee4ff88..56ee4ff88 100644
--- a/cli/tests/subdir/throwing_worker.js
+++ b/cli/tests/workers/throwing_worker.js
diff --git a/cli/tests/subdir/worker_crypto.js b/cli/tests/workers/worker_crypto.js
index a86340005..a86340005 100644
--- a/cli/tests/subdir/worker_crypto.js
+++ b/cli/tests/workers/worker_crypto.js
diff --git a/cli/tests/subdir/worker_globals.ts b/cli/tests/workers/worker_globals.ts
index a9e7efd85..a9e7efd85 100644
--- a/cli/tests/subdir/worker_globals.ts
+++ b/cli/tests/workers/worker_globals.ts
diff --git a/cli/tests/subdir/worker_unstable.ts b/cli/tests/workers/worker_unstable.ts
index a5b5f7ba2..a5b5f7ba2 100644
--- a/cli/tests/subdir/worker_unstable.ts
+++ b/cli/tests/workers/worker_unstable.ts
diff --git a/cli/tests/workers_round_robin_bench.ts b/cli/tests/workers_round_robin_bench.ts
index 8467480b8..461e86f91 100644
--- a/cli/tests/workers_round_robin_bench.ts
+++ b/cli/tests/workers_round_robin_bench.ts
@@ -22,7 +22,7 @@ async function main(): Promise<void> {
const workers: Array<[Map<number, Deferred<string>>, Worker]> = [];
for (let i = 1; i <= workerCount; ++i) {
const worker = new Worker(
- new URL("subdir/bench_worker.ts", import.meta.url).href,
+ new URL("workers/bench_worker.ts", import.meta.url).href,
{ type: "module" },
);
const promise = deferred();
diff --git a/cli/tests/workers_startup_bench.ts b/cli/tests/workers_startup_bench.ts
index 0d58b7912..f85bd13a1 100644
--- a/cli/tests/workers_startup_bench.ts
+++ b/cli/tests/workers_startup_bench.ts
@@ -5,7 +5,7 @@ async function bench(): Promise<void> {
const workers: Worker[] = [];
for (let i = 1; i <= workerCount; ++i) {
const worker = new Worker(
- new URL("subdir/bench_worker.ts", import.meta.url).href,
+ new URL("workers/bench_worker.ts", import.meta.url).href,
{ type: "module" },
);
const promise = new Promise<void>((resolve): void => {
diff --git a/cli/tests/workers_test.ts b/cli/tests/workers_test.ts
index 3bfe0181a..2dfc7e26b 100644
--- a/cli/tests/workers_test.ts
+++ b/cli/tests/workers_test.ts
@@ -2,12 +2,12 @@
// Requires to be run with `--allow-net` flag
-// FIXME(bartlomieju): this file is an integration test only because
-// workers are leaking ops at the moment - `worker.terminate()` is not
-// yet implemented. Once it gets implemented this file should be
-// again moved to `cli/js/` as an unit test file.
-
-import { assert, assertEquals } from "../../std/testing/asserts.ts";
+import {
+ assert,
+ assertEquals,
+ assertThrows,
+ fail,
+} from "../../std/testing/asserts.ts";
import { deferred } from "../../std/async/deferred.ts";
Deno.test({
@@ -16,11 +16,11 @@ Deno.test({
const promise = deferred();
const jsWorker = new Worker(
- new URL("subdir/test_worker.js", import.meta.url).href,
+ new URL("workers/test_worker.js", import.meta.url).href,
{ type: "module" },
);
const tsWorker = new Worker(
- new URL("subdir/test_worker.ts", import.meta.url).href,
+ new URL("workers/test_worker.ts", import.meta.url).href,
{ type: "module", name: "tsWorker" },
);
@@ -73,7 +73,7 @@ Deno.test({
const promise = deferred();
const nestedWorker = new Worker(
- new URL("subdir/nested_worker.js", import.meta.url).href,
+ new URL("workers/nested_worker.js", import.meta.url).href,
{ type: "module", name: "nested" },
);
@@ -93,7 +93,7 @@ Deno.test({
fn: async function (): Promise<void> {
const promise = deferred();
const throwingWorker = new Worker(
- new URL("subdir/throwing_worker.js", import.meta.url).href,
+ new URL("workers/throwing_worker.js", import.meta.url).href,
{ type: "module" },
);
@@ -114,7 +114,7 @@ Deno.test({
fn: async function (): Promise<void> {
const promise = deferred();
const w = new Worker(
- new URL("subdir/worker_globals.ts", import.meta.url).href,
+ new URL("workers/worker_globals.ts", import.meta.url).href,
{ type: "module" },
);
w.onmessage = (e): void => {
@@ -133,7 +133,7 @@ Deno.test({
const promise = deferred();
const fetchingWorker = new Worker(
- new URL("subdir/fetching_worker.js", import.meta.url).href,
+ new URL("workers/fetching_worker.js", import.meta.url).href,
{ type: "module" },
);
@@ -160,7 +160,7 @@ Deno.test({
const promise = deferred();
const busyWorker = new Worker(
- new URL("subdir/busy_worker.js", import.meta.url).href,
+ new URL("workers/busy_worker.js", import.meta.url).href,
{ type: "module" },
);
@@ -193,7 +193,7 @@ Deno.test({
const promise = deferred();
const racyWorker = new Worker(
- new URL("subdir/racy_worker.js", import.meta.url).href,
+ new URL("workers/racy_worker.js", import.meta.url).href,
{ type: "module" },
);
@@ -221,7 +221,7 @@ Deno.test({
const promise2 = deferred();
const worker = new Worker(
- new URL("subdir/event_worker.js", import.meta.url).href,
+ new URL("workers/event_worker.js", import.meta.url).href,
{ type: "module" },
);
@@ -265,7 +265,7 @@ Deno.test({
const promise1 = deferred();
const worker = new Worker(
- new URL("subdir/event_worker_scope.js", import.meta.url).href,
+ new URL("workers/event_worker_scope.js", import.meta.url).href,
{ type: "module" },
);
@@ -294,12 +294,18 @@ Deno.test({
const promise2 = deferred();
const regularWorker = new Worker(
- new URL("subdir/non_deno_worker.js", import.meta.url).href,
+ new URL("workers/non_deno_worker.js", import.meta.url).href,
{ type: "module" },
);
const denoWorker = new Worker(
- new URL("subdir/deno_worker.ts", import.meta.url).href,
- { type: "module", deno: true },
+ new URL("workers/deno_worker.ts", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: "inherit",
+ },
+ },
);
regularWorker.onmessage = (e): void => {
@@ -326,7 +332,7 @@ Deno.test({
fn: async function (): Promise<void> {
const promise = deferred();
const w = new Worker(
- new URL("subdir/worker_crypto.js", import.meta.url).href,
+ new URL("workers/worker_crypto.js", import.meta.url).href,
{ type: "module" },
);
w.onmessage = (e): void => {
@@ -344,7 +350,7 @@ Deno.test({
fn: async function (): Promise<void> {
const promise = deferred();
const w = new Worker(
- new URL("subdir/test_worker.ts", import.meta.url).href,
+ new URL("workers/test_worker.ts", import.meta.url).href,
{ type: "module", name: "tsWorker" },
);
const arr: number[] = [];
@@ -368,7 +374,7 @@ Deno.test({
fn: async function (): Promise<void> {
const promise = deferred();
const w = new Worker(
- new URL("./immediately_close_worker.js", import.meta.url).href,
+ new URL("./workers/immediately_close_worker.js", import.meta.url).href,
{ type: "module" },
);
setTimeout(() => {
@@ -378,3 +384,233 @@ Deno.test({
w.terminate();
},
});
+
+Deno.test("Worker inherits permissions", async function () {
+ const promise = deferred();
+ const worker = new Worker(
+ new URL("./workers/read_check_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: "inherit",
+ },
+ },
+ );
+
+ worker.onmessage = ({ data: hasPermission }) => {
+ assert(hasPermission);
+ promise.resolve();
+ };
+
+ worker.postMessage(null);
+
+ await promise;
+ worker.terminate();
+});
+
+Deno.test("Worker limit children permissions", async function () {
+ const promise = deferred();
+ const worker = new Worker(
+ new URL("./workers/read_check_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ read: false,
+ },
+ },
+ },
+ );
+
+ worker.onmessage = ({ data: hasPermission }) => {
+ assert(!hasPermission);
+ promise.resolve();
+ };
+
+ worker.postMessage(null);
+
+ await promise;
+ worker.terminate();
+});
+
+Deno.test("Worker limit children permissions granularly", async function () {
+ const promise = deferred();
+ const worker = new Worker(
+ new URL("./workers/read_check_granular_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ read: [
+ new URL("./workers/read_check_worker.js", import.meta.url),
+ ],
+ },
+ },
+ },
+ );
+
+ //Routes are relative to the spawned worker location
+ const routes = [
+ { permission: false, route: "read_check_granular_worker.js" },
+ { permission: true, route: "read_check_worker.js" },
+ ];
+
+ let checked = 0;
+ worker.onmessage = ({ data }) => {
+ checked++;
+ assertEquals(data.hasPermission, routes[data.index].permission);
+ routes.shift();
+ if (checked === routes.length) {
+ promise.resolve();
+ }
+ };
+
+ routes.forEach(({ route }, index) =>
+ worker.postMessage({
+ index,
+ route,
+ })
+ );
+
+ await promise;
+ worker.terminate();
+});
+
+Deno.test("Nested worker limit children permissions", async function () {
+ const promise = deferred();
+
+ /** This worker has read permissions but doesn't grant them to its children */
+ const worker = new Worker(
+ new URL("./workers/parent_read_check_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: "inherit",
+ },
+ },
+ );
+
+ worker.onmessage = ({ data }) => {
+ assert(data.parentHasPermission);
+ assert(!data.childHasPermission);
+ promise.resolve();
+ };
+
+ worker.postMessage(null);
+
+ await promise;
+ worker.terminate();
+});
+
+Deno.test("Nested worker limit children permissions granularly", async function () {
+ const promise = deferred();
+
+ /** This worker has read permissions but doesn't grant them to its children */
+ const worker = new Worker(
+ new URL("./workers/parent_read_check_granular_worker.js", import.meta.url)
+ .href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ read: [
+ new URL("./workers/read_check_granular_worker.js", import.meta.url),
+ ],
+ },
+ },
+ },
+ );
+
+ //Routes are relative to the spawned worker location
+ const routes = [
+ {
+ childHasPermission: false,
+ parentHasPermission: true,
+ route: "read_check_granular_worker.js",
+ },
+ {
+ childHasPermission: false,
+ parentHasPermission: false,
+ route: "read_check_worker.js",
+ },
+ ];
+
+ let checked = 0;
+ worker.onmessage = ({ data }) => {
+ checked++;
+ assertEquals(
+ data.childHasPermission,
+ routes[data.index].childHasPermission,
+ );
+ assertEquals(
+ data.parentHasPermission,
+ routes[data.index].parentHasPermission,
+ );
+ if (checked === routes.length) {
+ promise.resolve();
+ }
+ };
+
+ // Index needed cause requests will be handled asynchronously
+ routes.forEach(({ route }, index) =>
+ worker.postMessage({
+ index,
+ route,
+ })
+ );
+
+ await promise;
+ worker.terminate();
+});
+
+// This test relies on env permissions not being granted on main thread
+Deno.test("Worker initialization throws on worker permissions greater than parent thread permissions", function () {
+ assertThrows(
+ () => {
+ const worker = new Worker(
+ new URL("./workers/deno_worker.ts", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ env: true,
+ },
+ },
+ },
+ );
+ worker.terminate();
+ },
+ Deno.errors.PermissionDenied,
+ "Can't escalate parent thread permissions",
+ );
+});
+
+Deno.test("Worker with disabled permissions", async function () {
+ const promise = deferred();
+
+ const worker = new Worker(
+ new URL("./workers/no_permissions_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: false,
+ },
+ },
+ );
+
+ worker.onmessage = ({ data: sandboxed }) => {
+ assert(sandboxed);
+ promise.resolve();
+ };
+
+ worker.postMessage(null);
+ await promise;
+ worker.terminate();
+});