fix(api): validate route payloads for release checks

This commit is contained in:
diegosouzapw
2026-04-25 01:22:18 -03:00
parent f4d3cf3576
commit 57b926f739
6 changed files with 51 additions and 5 deletions

View File

@@ -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.

View File

@@ -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") {

View File

@@ -3042,8 +3042,20 @@ export default function ProviderDetailPage() {
<Card>
<div className="flex flex-col gap-3">
<div>
<h2 className="text-lg font-semibold">{t("upstreamProxyManagedTitle")}</h2>
<p className="text-sm text-text-muted mt-1">{t("upstreamProxyManagedDescription")}</p>
<h2 className="text-lg font-semibold">
{providerText(
t,
"upstreamProxyManagedTitle",
"Managed via Upstream Proxy Settings"
)}
</h2>
<p className="text-sm text-text-muted mt-1">
{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."
)}
</p>
</div>
<div className="flex flex-wrap gap-2">
<Link

View File

@@ -1,4 +1,5 @@
import { NextResponse } from "next/server";
import { z } from "zod";
import { CodexExecutor } from "@omniroute/open-sse/executors/codex.ts";
import { getApiKeyMetadata } from "@/lib/db/apiKeys";
import { authorizeWebSocketHandshake, extractWsTokenFromRequest } from "@/lib/ws/handshake";
@@ -11,6 +12,15 @@ const executor = new CodexExecutor();
type JsonRecord = Record<string, unknown>;
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");
}

View File

@@ -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)) {

View File

@@ -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"),