From 40ea6f70903dcfd96ecdff8dad6baae8b2696758 Mon Sep 17 00:00:00 2001 From: OG T Date: Sun, 7 Jun 2026 16:08:02 +0800 Subject: [PATCH] feat: separate internal and external open-task listing traffic --- apps/web/src/app/api/mcp/[tool]/route.ts | 45 ++++++++++++++++++++---- apps/web/src/app/api/open-tasks/route.ts | 35 +++++++++++++++--- 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/apps/web/src/app/api/mcp/[tool]/route.ts b/apps/web/src/app/api/mcp/[tool]/route.ts index 46a6d5a..e3b385f 100644 --- a/apps/web/src/app/api/mcp/[tool]/route.ts +++ b/apps/web/src/app/api/mcp/[tool]/route.ts @@ -28,6 +28,37 @@ const MCP_AGENT_HEADERS = [ "x-openai-agent", ]; +function resolveSourceIp(request: NextRequest) { + return ( + request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? + request.headers.get("x-real-ip") ?? + "unknown" + ); +} + +function isPrivateIp(ip: string | undefined) { + if (!ip) return false; + const normalized = ip.trim().toLowerCase(); + if (!normalized || normalized === "unknown" || normalized === "::1" || normalized === "localhost") { + return true; + } + + if (normalized.startsWith("127.") || normalized.startsWith("10.") || normalized.startsWith("192.168.")) { + return true; + } + + if (normalized.startsWith("172.")) { + const secondOctet = Number(normalized.split(".")[1]); + return secondOctet >= 16 && secondOctet <= 31; + } + + if (normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80")) { + return true; + } + + return false; +} + function normalizeActorId(value: string, fallback: string) { const normalized = value.trim().toLowerCase().replace(/[^a-z0-9._:-]+/g, "_").replace(/_+/g, "_"); return normalized.slice(0, 64) || fallback; @@ -72,6 +103,9 @@ export async function POST(request: NextRequest, props: { params: Promise<{ tool case "list_open_tasks": { ListOpenTasksRequestSchema.parse(body); const actor = resolveActorFromMcpRequest(request); + const sourceIp = resolveSourceIp(request); + const isPublicIp = !isPrivateIp(sourceIp); + const trafficAction = isPublicIp ? "EXTERNAL_LIST_OPEN_TASKS_MCP" : "INTERNAL_LIST_OPEN_TASKS_MCP"; const tasks = await prisma.task.findMany({ where: { status: TaskStatus.OPEN }, @@ -95,7 +129,7 @@ export async function POST(request: NextRequest, props: { params: Promise<{ tool void sendTrafficAlert({ level: "info", - action: "EXTERNAL_LIST_OPEN_TASKS_MCP", + action: trafficAction, surface: "mcp/list_open_tasks", actorType: actor.actorType, actorId: actor.actorId, @@ -104,11 +138,8 @@ export async function POST(request: NextRequest, props: { params: Promise<{ tool metadata: { count: formattedTasks.length, source_tool: tool, + source_ip: sourceIp, user_agent: request.headers.get("user-agent") ?? "unknown", - source_ip: - request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? - request.headers.get("x-real-ip") ?? - "unknown", }, }); @@ -117,13 +148,13 @@ export async function POST(request: NextRequest, props: { params: Promise<{ tool createdAt: { gte: new Date(Date.now() - MCP_SURGE_WINDOW_MINUTES * 60 * 1000), }, - action: "EXTERNAL_LIST_OPEN_TASKS_MCP", + action: trafficAction, }, }).then((eventCount) => { if (eventCount > 0 && eventCount % MCP_SURGE_INTERVAL === 0) { void sendTrafficAlert({ level: "warning", - action: "EXTERNAL_LIST_OPEN_TASKS_SURGE", + action: isPublicIp ? "EXTERNAL_LIST_OPEN_TASKS_SURGE" : "INTERNAL_LIST_OPEN_TASKS_SURGE", surface: "mcp/list_open_tasks", actorType: "SYSTEM", actorId: "traffic-monitor", diff --git a/apps/web/src/app/api/open-tasks/route.ts b/apps/web/src/app/api/open-tasks/route.ts index 0ebefdc..84b51a6 100644 --- a/apps/web/src/app/api/open-tasks/route.ts +++ b/apps/web/src/app/api/open-tasks/route.ts @@ -49,6 +49,29 @@ function resolveSourceIp(request: Request) { ); } +function isPrivateIp(ip: string | undefined) { + if (!ip) return false; + const normalized = ip.trim().toLowerCase(); + if (!normalized || normalized === "unknown" || normalized === "::1" || normalized === "localhost") { + return true; + } + + if (normalized.startsWith("127.") || normalized.startsWith("10.") || normalized.startsWith("192.168.")) { + return true; + } + + if (normalized.startsWith("172.")) { + const secondOctet = Number(normalized.split(".")[1]); + return secondOctet >= 16 && secondOctet <= 31; + } + + if (normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80")) { + return true; + } + + return false; +} + function isLikelyAIAgentUserAgent(userAgent: string) { const normalized = userAgent.toLowerCase(); return AI_USER_AGENT_HINTS.some((token) => normalized.includes(token)); @@ -83,6 +106,10 @@ function resolveExternalActor(request: Request) { } export async function GET(request: Request) { + const sourceIp = resolveSourceIp(request); + const isPublicIp = !isPrivateIp(sourceIp); + const trafficAction = isPublicIp ? "EXTERNAL_LIST_OPEN_TASKS" : "INTERNAL_LIST_OPEN_TASKS"; + const tasks = await prisma.task.findMany({ where: { status: TaskStatus.OPEN }, orderBy: { created_at: "desc" }, @@ -124,7 +151,7 @@ export async function GET(request: Request) { void sendTrafficAlert({ level: "info", - action: "EXTERNAL_LIST_OPEN_TASKS", + action: trafficAction, surface: "public-open-tasks", actorType: actor.actorType, actorId: actor.actorId, @@ -133,7 +160,7 @@ export async function GET(request: Request) { metadata: { source: "public-open-tasks", task_count: publicPayload.length, - source_ip: resolveSourceIp(request), + source_ip: sourceIp, user_agent: request.headers.get("user-agent") ?? "unknown", }, }); @@ -145,13 +172,13 @@ export async function GET(request: Request) { void prisma.auditEvent.count({ where: { createdAt: { gte: surgeWindowStart }, - action: "EXTERNAL_LIST_OPEN_TASKS", + action: trafficAction, }, }).then((eventCount) => { if (eventCount > 0 && eventCount % 25 === 0) { void sendTrafficAlert({ level: "warning", - action: "EXTERNAL_LIST_OPEN_TASKS_SURGE", + action: isPublicIp ? "EXTERNAL_LIST_OPEN_TASKS_SURGE" : "INTERNAL_LIST_OPEN_TASKS_SURGE", surface: "public-open-tasks", actorType: "SYSTEM", actorId: "traffic-monitor",