From 60baaf95b6b64fc309e697d0f704d3c543a7bd16 Mon Sep 17 00:00:00 2001 From: lalamdi Date: Wed, 26 Aug 2026 12:44:42 +0000 Subject: [PATCH 1/3] Add Bun development container --- .devcontainer/devcontainer-lock.json | 9 +++++++++ .devcontainer/devcontainer.json | 8 ++++++++ 2 files changed, 17 insertions(+) create mode 100644 .devcontainer/devcontainer-lock.json create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 00000000..62fb41f0 --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,9 @@ +{ + "features": { + "ghcr.io/devcontainers/features/git:1": { + "version": "1.3.8", + "resolved": "ghcr.io/devcontainers/features/git@sha256:fd75977de13a9979000e0e78baf949adb0ca71d2398995fa22e0a36d7e7e7fe2", + "integrity": "sha256:fd75977de13a9979000e0e78baf949adb0ca71d2398995fa22e0a36d7e7e7fe2" + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..d74a7325 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,8 @@ +{ + "name": "OpenCode Dev Container", + "image": "oven/bun:1.3", + "features": { + "ghcr.io/devcontainers/features/git:1": {} + }, + "postCreateCommand": "apt-get update && apt-get install -y python3 make g++" +} \ No newline at end of file From 09293e03afe591e40fd6c3df2e1a4b5f97cb0e5e Mon Sep 17 00:00:00 2001 From: lalamdi Date: Tue, 1 Sep 2026 19:05:27 +0000 Subject: [PATCH 2/3] Refactor CORS origin validation and add tests --- packages/server/src/cors.ts | 16 +++++---- packages/server/test/cors.test.ts | 55 +++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 packages/server/test/cors.test.ts diff --git a/packages/server/src/cors.ts b/packages/server/src/cors.ts index 92296a3b..57d31001 100644 --- a/packages/server/src/cors.ts +++ b/packages/server/src/cors.ts @@ -1,6 +1,9 @@ import { Context } from "effect" const opencodeOrigin = /^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/ +const allowedOriginPrefixes = ["http://localhost:", "http://127.0.0.1:", "oc://renderer"] + +const allowedOrigins = new Set(["tauri://localhost", "http://tauri.localhost", "https://tauri.localhost"]) export type CorsOptions = { readonly cors?: ReadonlyArray } @@ -8,14 +11,15 @@ export const CorsConfig = Context.Reference("@opencode/ defaultValue: () => undefined, }) +function isBuiltInAllowedOrigin(input: string) { + if (allowedOriginPrefixes.some((prefix) => input.startsWith(prefix))) return true + if (allowedOrigins.has(input)) return true + return opencodeOrigin.test(input) +} + export function isAllowedCorsOrigin(input: string | undefined, opts?: CorsOptions) { if (!input) return true - if (input.startsWith("http://localhost:")) return true - if (input.startsWith("http://127.0.0.1:")) return true - if (input.startsWith("oc://renderer")) return true - if (input === "tauri://localhost" || input === "http://tauri.localhost" || input === "https://tauri.localhost") - return true - if (opencodeOrigin.test(input)) return true + if (isBuiltInAllowedOrigin(input)) return true return opts?.cors?.includes(input) ?? false } diff --git a/packages/server/test/cors.test.ts b/packages/server/test/cors.test.ts new file mode 100644 index 00000000..b469f9a7 --- /dev/null +++ b/packages/server/test/cors.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, test } from "bun:test" +import { isAllowedCorsOrigin, isAllowedRequestOrigin } from "../src/cors" + +describe("isAllowedCorsOrigin", () => { + test("allows requests without an origin", () => { + expect(isAllowedCorsOrigin(undefined)).toBe(true) + }) + + test("allows built-in local and application origins", () => { + const allowedOrigins = [ + "http://localhost:3000", + "http://127.0.0.1:4096", + "oc://renderer", + "tauri://localhost", + "http://tauri.localhost", + "https://tauri.localhost", + ] + + for (const origin of allowedOrigins) { + expect(isAllowedCorsOrigin(origin)).toBe(true) + } + }) + + test("allows official OpenCode origins", () => { + expect(isAllowedCorsOrigin("https://opencode.ai")).toBe(true) + expect(isAllowedCorsOrigin("https://docs.opencode.ai")).toBe(true) + }) + + test("allows origins provided through configuration", () => { + const options = { cors: ["https://allowed.example.com"] } + + expect(isAllowedCorsOrigin("https://allowed.example.com", options)).toBe(true) + }) + test("rejects origins that are not allowed", () => { + expect(isAllowedCorsOrigin("https://example.com")).toBe(false) + expect(isAllowedCorsOrigin("https://opencode.ai.example.com")).toBe(false) + }) +}) + +describe("isAllowedRequestOrigin", () => { + test("allows requests without an origin", () => { + expect(isAllowedRequestOrigin(undefined, undefined)).toBe(true) + }) + + test("allows requests from the same host", () => { + expect(isAllowedRequestOrigin("https://example.com", "example.com")).toBe(true) + }) + + test("falls back to CORS validation for a different or invalid host", () => { + const options = { cors: ["https://allowed.example.com"] } + + expect(isAllowedRequestOrigin("https://allowed.example.com", "different.example.com", options)).toBe(true) + expect(isAllowedRequestOrigin("not-a-valid-url", "example.com")).toBe(false) + }) +}) \ No newline at end of file From 297acd666265d787475b0f0f1352511f63f558cc Mon Sep 17 00:00:00 2001 From: lalamdi Date: Sat, 5 Sep 2026 11:13:16 +0000 Subject: [PATCH 3/3] Run server tests in CI --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6410217e..78248980 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,6 +64,10 @@ jobs: # contention issue typecheck.yml already caps for tsgo. run: GITHUB_ACTIONS=false bun turbo test --concurrency=4 + - name: Run server tests + working-directory: packages/server + run: bun test + - name: Check generated client timeout-minutes: 5 working-directory: packages/client