From 7eeeb5e46c68b0130c84e752320574f78cea28e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 06:44:19 +0000 Subject: [PATCH 1/2] Initial plan From 3c1f1ced39b20a930547be9ecf94bd2a916bbe3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 06:47:55 +0000 Subject: [PATCH 2/2] fix(api): mask error.message in proxy, upload, and langsmith routes Extract internalErrorResponse helper that logs full error server-side but returns a generic "Internal server error" to clients, preventing leakage of internal hostnames, ports, file-system paths, and API keys. Agent-Logs-Url: https://github.com/teddynote-lab/langgraph-chat-ui/sessions/1e9c771d-26ba-486d-aab0-155cba18d45f Co-authored-by: teddylee777 <10074379+teddylee777@users.noreply.github.com> --- frontend/src/app/api/[..._path]/route.ts | 4 ++-- frontend/src/app/api/admin/upload/route.ts | 9 ++------- frontend/src/app/api/langsmith/runs/route.ts | 7 ++----- frontend/src/app/api/upload/route.ts | 9 ++------- frontend/src/lib/api/error-response.ts | 14 ++++++++++++++ 5 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 frontend/src/lib/api/error-response.ts diff --git a/frontend/src/app/api/[..._path]/route.ts b/frontend/src/app/api/[..._path]/route.ts index 2ccf6ed..c4aae3a 100644 --- a/frontend/src/app/api/[..._path]/route.ts +++ b/frontend/src/app/api/[..._path]/route.ts @@ -6,6 +6,7 @@ import { resolveApiUrl } from "@/lib/connections/resolve"; import { getAuthMode, usesNextAuth } from "@/types/auth-mode"; import { generateUserJWT } from "@/lib/auth/jwt"; import { isPrivateUrl } from "@/lib/utils/url-validation"; +import { internalErrorResponse } from "@/lib/api/error-response"; function getCorsHeaders(req: NextRequest) { const origin = req.headers.get("origin"); @@ -156,8 +157,7 @@ async function handleRequest(req: NextRequest, method: string) { }, }); } catch (e) { - const error = e as Error; - return NextResponse.json({ error: error.message }, { status: 500 }); + return internalErrorResponse(e, "proxy request failed"); } } diff --git a/frontend/src/app/api/admin/upload/route.ts b/frontend/src/app/api/admin/upload/route.ts index b257dee..7c9aedf 100644 --- a/frontend/src/app/api/admin/upload/route.ts +++ b/frontend/src/app/api/admin/upload/route.ts @@ -3,6 +3,7 @@ import { auth } from "@/lib/auth"; import { isAdmin } from "@/types/auth-mode"; import type { UserRole } from "@/types/auth-mode"; import { writeFile, mkdir, access, constants } from "fs/promises"; +import { internalErrorResponse } from "@/lib/api/error-response"; import path from "path"; const ALLOWED_TYPES = [ @@ -96,13 +97,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ url, filename }); } catch (error) { - console.error("Upload error:", error); - return NextResponse.json( - { - error: `Failed to upload file: ${error instanceof Error ? error.message : "Unknown error"}`, - }, - { status: 500 }, - ); + return internalErrorResponse(error, "admin file upload failed"); } } diff --git a/frontend/src/app/api/langsmith/runs/route.ts b/frontend/src/app/api/langsmith/runs/route.ts index 148b95a..2c5908d 100644 --- a/frontend/src/app/api/langsmith/runs/route.ts +++ b/frontend/src/app/api/langsmith/runs/route.ts @@ -2,6 +2,7 @@ import { Client, type Run } from "langsmith"; import { NextRequest, NextResponse } from "next/server"; import { LangSmithRun, buildRunHierarchy } from "@/types/langsmith"; import { usesNextAuth } from "@/types/auth-mode"; +import { internalErrorResponse } from "@/lib/api/error-response"; // LangSmith Run 객체를 LangSmithRun 형식으로 변환 function convertRun(run: Run): LangSmithRun { @@ -137,10 +138,6 @@ export async function GET(req: NextRequest) { return NextResponse.json({ runs, hierarchy }); } catch (error) { - console.error("Failed to fetch LangSmith runs:", error); - return NextResponse.json( - { error: error instanceof Error ? error.message : "Unknown error" }, - { status: 500 }, - ); + return internalErrorResponse(error, "LangSmith runs fetch failed"); } } diff --git a/frontend/src/app/api/upload/route.ts b/frontend/src/app/api/upload/route.ts index 6ce07c3..fbff943 100644 --- a/frontend/src/app/api/upload/route.ts +++ b/frontend/src/app/api/upload/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { auth } from "@/lib/auth"; import { usesNextAuth } from "@/types/auth-mode"; import { getSetting } from "@/lib/services/settings.service"; +import { internalErrorResponse } from "@/lib/api/error-response"; import { writeFile, mkdir, access, constants } from "fs/promises"; import path from "path"; @@ -106,13 +107,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ url, filename }); } catch (error) { - console.error("Upload error:", error); - return NextResponse.json( - { - error: `Failed to upload file: ${error instanceof Error ? error.message : "Unknown error"}`, - }, - { status: 500 }, - ); + return internalErrorResponse(error, "user file upload failed"); } } diff --git a/frontend/src/lib/api/error-response.ts b/frontend/src/lib/api/error-response.ts new file mode 100644 index 0000000..cbd8abe --- /dev/null +++ b/frontend/src/lib/api/error-response.ts @@ -0,0 +1,14 @@ +import { NextResponse } from "next/server"; + +/** + * Return a generic 500 JSON response while logging the real error server-side. + * Prevents leaking internal hostnames, ports, file-system paths, or API keys + * to clients (OWASP A05:2021 — Security Misconfiguration). + */ +export function internalErrorResponse(error: unknown, context: string) { + console.error(`[api] ${context}`, error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); +}