From 45f796ac34b4d13c01238bc105901c0f9c2caaf6 Mon Sep 17 00:00:00 2001 From: Scott Twiname Date: Mon, 14 Oct 2024 16:30:18 +1300 Subject: [PATCH] Further restrict web worker permissions --- src/app.ts | 4 +-- src/info.ts | 8 +++-- src/loader.ts | 10 +++--- src/sandbox/index.ts | 37 +++++++++++++++++++++-- src/sandbox/webWorker/webWorkerSandbox.ts | 20 +++++++++--- src/util.ts | 3 ++ 6 files changed, 65 insertions(+), 17 deletions(-) diff --git a/src/app.ts b/src/app.ts index 699be3c..aa09524 100644 --- a/src/app.ts +++ b/src/app.ts @@ -23,13 +23,13 @@ export async function runApp(config: { forceReload?: boolean; }): Promise { const model = new Ollama({ host: config.host }); - const projectPath = await loadProject( + const [projectPath, source] = await loadProject( config.projectPath, config.ipfs, undefined, config.forceReload, ); - const sandbox = await getDefaultSandbox(resolve(projectPath)); + const sandbox = await getDefaultSandbox(resolve(projectPath), source); const pendingCtx = makeContext( sandbox, diff --git a/src/info.ts b/src/info.ts index ad017b0..fdc661e 100644 --- a/src/info.ts +++ b/src/info.ts @@ -5,12 +5,14 @@ import type { IProject } from "./project/project.ts"; import type { TSchema } from "@sinclair/typebox"; import type { IPFSClient } from "./ipfs.ts"; import { loadProject } from "./loader.ts"; +import type { ProjectSource } from "./util.ts"; export async function getProjectJson( projectPath: string, + source: ProjectSource, sandboxFactory = getDefaultSandbox, ): Promise & { tools: string[]; config?: TSchema }> { - const sandbox = await sandboxFactory(resolve(projectPath)); + const sandbox = await sandboxFactory(resolve(projectPath), source); return { model: sandbox.model, @@ -26,8 +28,8 @@ export async function projectInfo( ipfs: IPFSClient, json = false, ): Promise { - const loadedPath = await loadProject(projectPath, ipfs); - const projectJson = await getProjectJson(loadedPath); + const [loadedPath, source] = await loadProject(projectPath, ipfs); + const projectJson = await getProjectJson(loadedPath, source); if (json) { console.log(JSON.stringify( diff --git a/src/loader.ts b/src/loader.ts index f251980..35af8dd 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -3,7 +3,7 @@ import { CIDReg, type IPFSClient } from "./ipfs.ts"; import { resolve } from "@std/path/resolve"; import { UntarStream } from "@std/tar"; import { ensureDir, exists } from "@std/fs"; -import { getSpinner } from "./util.ts"; +import { getSpinner, type ProjectSource } from "./util.ts"; export const getOSTempDir = () => Deno.env.get("TMPDIR") || Deno.env.get("TMP") || Deno.env.get("TEMP") || @@ -14,7 +14,7 @@ export async function loadProject( ipfs: IPFSClient, tmpDir?: string, forceReload?: boolean, -): Promise { +): Promise<[string, ProjectSource]> { if (CIDReg.test(projectPath)) { const spinner = getSpinner().start("Loading project from IPFS"); try { @@ -25,7 +25,7 @@ export async function loadProject( // Early exit if the file has already been fetched if (!forceReload && (await exists(filePath))) { spinner.succeed("Loaded project from IPFS"); - return filePath; + return [filePath, "ipfs"]; } await ensureDir(tmp); @@ -36,14 +36,14 @@ export async function loadProject( spinner.succeed("Loaded project from IPFS"); - return filePath; + return [filePath, "ipfs"]; } catch (e) { spinner.fail("Failed to load project"); throw e; } } - return resolve(projectPath); + return [resolve(projectPath), "local"]; } export async function loadVectorStoragePath( diff --git a/src/sandbox/index.ts b/src/sandbox/index.ts index 8d28cee..30b051a 100644 --- a/src/sandbox/index.ts +++ b/src/sandbox/index.ts @@ -1,13 +1,44 @@ +import type { ProjectSource } from "../util.ts"; import type { ISandbox } from "./sandbox.ts"; -import { WebWorkerSandbox } from "./webWorker/webWorkerSandbox.ts"; +import { + type Permissions, + WebWorkerSandbox, +} from "./webWorker/webWorkerSandbox.ts"; export * from "./sandbox.ts"; export * from "./mockSandbox.ts"; export * from "./unsafeSandbox.ts"; -export function getDefaultSandbox(path: string): Promise { +const IPFS_PERMISSIONS: Permissions = { + allowRead: false, + allowFFI: false, +}; + +const LOCAL_PERMISSIONS: Permissions = { + allowRead: true, + allowFFI: true, +}; + +function getPermisionsForSource(source: ProjectSource): Permissions { + switch (source) { + case "local": + return LOCAL_PERMISSIONS; + case "ipfs": + return IPFS_PERMISSIONS; + default: + throw new Error( + `Unable to set permissions for unknown source: ${source}`, + ); + } +} + +export function getDefaultSandbox( + path: string, + source: ProjectSource, +): Promise { // return UnsafeSandbox.create(path); - return WebWorkerSandbox.create(path); + const permissions = getPermisionsForSource(source); + return WebWorkerSandbox.create(path, permissions); } export { WebWorkerSandbox }; diff --git a/src/sandbox/webWorker/webWorkerSandbox.ts b/src/sandbox/webWorker/webWorkerSandbox.ts index 2883eac..90500fe 100644 --- a/src/sandbox/webWorker/webWorkerSandbox.ts +++ b/src/sandbox/webWorker/webWorkerSandbox.ts @@ -18,13 +18,26 @@ import { loadConfigFromEnv } from "../../util.ts"; import { FromSchema } from "../../fromSchema.ts"; import type { IContext } from "../../context/context.ts"; import type { IVectorConfig } from "../../project/project.ts"; +import { dirname } from "@std/path/dirname"; + +export type Permissions = { + /** + * For local projects allow reading all locations for imports to work. + * TODO: This could be limited to the project dir + DENO_DIR cache but DENO_DIR doesn't provide the default currently + */ + allowRead?: boolean; + allowFFI?: boolean; +}; export class WebWorkerSandbox implements ISandbox { #connection: rpc.MessageConnection; #config: TSchema | undefined; #tools: Tool[]; - public static async create(path: string): Promise { + public static async create( + path: string, + permissions?: Permissions, + ): Promise { const w = new Worker( import.meta.resolve("./webWorker.ts"), { @@ -32,10 +45,9 @@ export class WebWorkerSandbox implements ISandbox { deno: { permissions: { env: false, // Should be passed through in loadConfigFromEnv below - // hrtime: false, net: "inherit", // TODO remove localhost - ffi: true, // Needed for node js ffi - read: true, // Needed for imports to node modules + ffi: permissions?.allowFFI ?? false, // Needed for node js ffi, TODO this could be the same as read permissions + read: permissions?.allowRead ? true : [dirname(path)], run: false, write: false, }, diff --git a/src/util.ts b/src/util.ts index 968bde2..cfffe64 100644 --- a/src/util.ts +++ b/src/util.ts @@ -32,3 +32,6 @@ export function getPrompt(): string | null { return response; } + +// Possible sources where projects can be loaded from +export type ProjectSource = "local" | "ipfs";