diff --git a/CHANGELOG.md b/CHANGELOG.md index dfca5b35b3..3edc833ca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ ### 🐛 Bug Fixes - **fix(mitm):** Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime. +- **fix(build):** Move the local `.tmp/wine32` Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot trigger `EACCES` scans during Node 24 builds. +- **fix(api):** Validate the Codex Responses websocket bridge and `/v1/batches` JSON payloads with Zod before use, keeping `request.json()` route validation green and returning explicit 400 responses for invalid bodies. +- **fix(ui):** Keep the upstream proxy provider detail page labeled with a fallback "Managed via Upstream Proxy Settings" management surface when translations are unavailable. - **fix(electron):** Harden the production desktop CSP by removing `unsafe-eval` outside development and adding object, base URI, form action, frame ancestor, and worker restrictions. - **fix(cli):** Replace shell-interpolated setup and privileged command execution paths with argument-based `spawn`/`execFile` helpers for database setup, Tailscale sudo commands, MITM DNS edits, and certificate install/uninstall flows. - **fix(ui):** Keep provider icons resilient by using direct `@lobehub/icons` components first, then local PNG/SVG fallbacks, avoiding the `@lobehub/ui` peer runtime in the dashboard. diff --git a/scripts/build-next-isolated.mjs b/scripts/build-next-isolated.mjs index f811076364..1d8d591013 100644 --- a/scripts/build-next-isolated.mjs +++ b/scripts/build-next-isolated.mjs @@ -23,6 +23,11 @@ export function getTransientBuildPaths(rootDir = projectRoot, env = process.env) sourcePath: path.join(rootDir, "app"), backupPath: path.join(backupRoot, "app"), }, + { + label: "local Wine prefix", + sourcePath: path.join(rootDir, ".tmp", "wine32"), + backupPath: path.join(backupRoot, "wine32"), + }, ]; if (env.OMNIROUTE_BUILD_MOVE_TASKS === "1") { diff --git a/src/app/(dashboard)/dashboard/providers/[id]/page.tsx b/src/app/(dashboard)/dashboard/providers/[id]/page.tsx index 37b3e4218d..b0effebe32 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/page.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/page.tsx @@ -3042,8 +3042,20 @@ export default function ProviderDetailPage() {
-

{t("upstreamProxyManagedTitle")}

-

{t("upstreamProxyManagedDescription")}

+

+ {providerText( + t, + "upstreamProxyManagedTitle", + "Managed via Upstream Proxy Settings" + )} +

+

+ {providerText( + t, + "upstreamProxyManagedDescription", + "CLIProxyAPI is configured as an upstream proxy layer, not as a direct provider connection. Manage the binary/runtime in CLI Tools and enable proxy routing on each provider via the provider proxy controls." + )} +

; +const bridgePayloadSchema = z + .object({ + action: z.string().optional(), + requestUrl: z.string().optional(), + headers: z.record(z.string(), z.unknown()).optional(), + response: z.record(z.string(), z.unknown()).optional(), + }) + .passthrough(); + function isRecord(value: unknown): value is JsonRecord { return typeof value === "object" && value !== null && !Array.isArray(value); } @@ -168,7 +178,11 @@ export async function POST(request: Request) { let body: JsonRecord; try { const parsed = await request.json(); - body = isRecord(parsed) ? parsed : {}; + const validation = bridgePayloadSchema.safeParse(parsed); + if (!validation.success) { + return jsonError(400, "invalid_json", "Request body must be a JSON object"); + } + body = validation.data; } catch { return jsonError(400, "invalid_json", "Request body must be JSON"); } diff --git a/src/app/api/v1/batches/route.ts b/src/app/api/v1/batches/route.ts index 39d24b619d..5882b5d7c6 100644 --- a/src/app/api/v1/batches/route.ts +++ b/src/app/api/v1/batches/route.ts @@ -46,7 +46,19 @@ export async function POST(request: Request) { try { const body = await request.json(); - const validated = v1BatchCreateSchema.parse(body); + const validation = v1BatchCreateSchema.safeParse(body); + if (!validation.success) { + return NextResponse.json( + { + error: { + message: validation.error.message, + type: "invalid_request_error", + }, + }, + { status: 400, headers: CORS_HEADERS } + ); + } + const validated = validation.data; const inputFile = getFile(validated.input_file_id); if (!inputFile || (inputFile.apiKeyId !== null && inputFile.apiKeyId !== apiKeyId)) { diff --git a/tests/unit/build-next-isolated.test.ts b/tests/unit/build-next-isolated.test.ts index 64f22ede3e..2cbd2350ab 100644 --- a/tests/unit/build-next-isolated.test.ts +++ b/tests/unit/build-next-isolated.test.ts @@ -108,7 +108,7 @@ test("getTransientBuildPaths leaves _tasks in place by default", () => { assert.deepEqual( paths.map((entry) => entry.label), - ["legacy app snapshot"] + ["legacy app snapshot", "local Wine prefix"] ); assert.equal( paths.some((entry) => entry.sourcePath === "/repo/_tasks"),