diff --git a/packages/core/src/database/database.ts b/packages/core/src/database/database.ts index 249152333da..1f02ae29dc5 100644 --- a/packages/core/src/database/database.ts +++ b/packages/core/src/database/database.ts @@ -5,8 +5,6 @@ import { sqliteLayer, supportsForeignKeyToggle, supportsTuningPragmas } from "#s import { Context, Effect, Layer, Schema, Semaphore } from "effect" import type { SqlClient } from "effect/unstable/sql" import { Global } from "@opencode/util/global" -import { closeSync, existsSync, openSync } from "node:fs" -import { chmod } from "node:fs/promises" import { isAbsolute, join } from "path" import { DatabaseMigration } from "./migration.js" import { makeGlobalNode } from "@opencode/util/effect/app-node" @@ -72,31 +70,13 @@ export function layer(options: Options = { path: ":memory:" }) { Layer.provide(sqliteLayer({ filename })), ) const filename = options.path ?? ":memory:" - if (filename === ":memory:") return provide(filename) - const file = isAbsolute(filename) ? filename : join((yield* Global.Service).data, filename) - yield* Effect.promise(() => restrictToOwner(file)) - return provide(file) + if (filename === ":memory:" || isAbsolute(filename)) return provide(filename) + const global = yield* Global.Service + return provide(join(global.data, filename)) }), ) } -// SQLite creates new sidecars with the database's mode, but does not tighten existing sidecars. -// Windows relies on the user profile directory's inherited ACLs instead of POSIX modes. -async function restrictToOwner(filename: string) { - if (process.platform === "win32") return - // Opening and closing an existing database can release another connection's POSIX locks. - // Create missing files synchronously so another fiber cannot open one before it is restricted. - if (!existsSync(filename)) closeSync(openSync(filename, "a", 0o600)) - await chmod(filename, 0o600) - await Promise.all( - [`${filename}-wal`, `${filename}-shm`].map((file) => - chmod(file, 0o600).catch((error: NodeJS.ErrnoException) => { - if (error.code !== "ENOENT") throw error - }), - ), - ) -} - // The database service over an injected SqlClient, for runtimes that receive // database storage instead of opening a filesystem path. Any client provided // here still goes through the pragma guards and migrations; Global is required diff --git a/packages/core/test/database.test.ts b/packages/core/test/database.test.ts deleted file mode 100644 index 7a8df62493f..00000000000 --- a/packages/core/test/database.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { chmod, stat, writeFile } from "node:fs/promises" -import path from "path" -import { Effect, Layer } from "effect" -import { Database } from "@opencode/core/database/database" -import { Global } from "@opencode/util/global" -import { tmpdir } from "./fixture/tmpdir" - -// Read while the connection is open: SQLite may remove the sidecars on close. -const openModes = (filename: string) => - Effect.runPromise( - Effect.gen(function* () { - yield* Layer.build(Database.layer({ path: filename })) - return yield* Effect.promise(() => - Promise.all(["", "-wal", "-shm"].map(async (suffix) => (await stat(filename + suffix)).mode & 0o777)), - ) - }).pipe(Effect.scoped, Effect.provideService(Global.Service, Global.make({ data: path.dirname(filename) }))), - ) - -describe.skipIf(process.platform === "win32")("Database file permissions", () => { - test("creates the database and sidecars readable only by the owner", async () => { - await using tmp = await tmpdir() - - expect(await openModes(path.join(tmp.path, "opencode.db"))).toEqual([0o600, 0o600, 0o600]) - }) - - test("tightens an existing database and sidecars", async () => { - await using tmp = await tmpdir() - const filename = path.join(tmp.path, "opencode.db") - await Promise.all( - ["", "-wal", "-shm"].map(async (suffix) => { - await writeFile(filename + suffix, "") - await chmod(filename + suffix, 0o644) - }), - ) - - expect(await openModes(filename)).toEqual([0o600, 0o600, 0o600]) - }) -})