chore: harden external traffic attribution and clean external monitor output
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
TaskStatus,
|
||||
JudgeOverallResult
|
||||
} from "@agent-bounty/contracts";
|
||||
import { isIP } from "node:net";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { runSubmissionInSandbox } from "@/lib/sandbox";
|
||||
import { logAuditEvent } from "@/lib/audit";
|
||||
@@ -28,20 +29,55 @@ const MCP_AGENT_HEADERS = [
|
||||
"x-openai-agent",
|
||||
];
|
||||
|
||||
function sanitizeIpAddress(value: string | undefined) {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const first = value.split(",")[0]?.trim();
|
||||
if (!first) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const bracketedMatch = first.match(/^\[(.+)\]:(\d+)$/);
|
||||
if (bracketedMatch?.[1]) {
|
||||
return bracketedMatch[1].toLowerCase();
|
||||
}
|
||||
|
||||
const ipv4WithPortMatch = first.match(/^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/);
|
||||
if (ipv4WithPortMatch?.[1]) {
|
||||
return ipv4WithPortMatch[1];
|
||||
}
|
||||
|
||||
return first.toLowerCase();
|
||||
}
|
||||
|
||||
function resolveSourceIp(request: NextRequest) {
|
||||
return (
|
||||
request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ??
|
||||
return sanitizeIpAddress(
|
||||
request.headers.get("x-forwarded-for") ??
|
||||
request.headers.get("x-real-ip") ??
|
||||
request.headers.get("cf-connecting-ip") ??
|
||||
request.headers.get("true-client-ip") ??
|
||||
request.headers.get("x-client-ip") ??
|
||||
"unknown"
|
||||
);
|
||||
}
|
||||
|
||||
function isPrivateIp(ip: string | undefined) {
|
||||
if (!ip) return false;
|
||||
if (!ip) return true;
|
||||
const normalized = ip.trim().toLowerCase();
|
||||
if (!normalized || normalized === "unknown" || normalized === "::1" || normalized === "localhost") {
|
||||
if (!normalized || normalized === "unknown" || normalized === "localhost") {
|
||||
return true;
|
||||
}
|
||||
if (normalized === "::1" || normalized.startsWith("fe80")) {
|
||||
return true;
|
||||
}
|
||||
if (isIP(normalized) === 6 && (normalized.startsWith("fc") || normalized.startsWith("fd"))) {
|
||||
return true;
|
||||
}
|
||||
if (isIP(normalized) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (normalized.startsWith("127.") || normalized.startsWith("10.") || normalized.startsWith("192.168.")) {
|
||||
return true;
|
||||
@@ -52,10 +88,6 @@ function isPrivateIp(ip: string | undefined) {
|
||||
return secondOctet >= 16 && secondOctet <= 31;
|
||||
}
|
||||
|
||||
if (normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { TaskStatus } from "@agent-bounty/contracts";
|
||||
import { sendTrafficAlert } from "@/lib/traffic-alert";
|
||||
import { isIP } from "node:net";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -41,21 +42,56 @@ function normalizeActorId(value: string, fallback: string) {
|
||||
return cleaned.slice(0, 64) || fallback;
|
||||
}
|
||||
|
||||
function sanitizeIpAddress(value: string | undefined) {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const first = value.split(",")[0]?.trim();
|
||||
if (!first) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const bracketedMatch = first.match(/^\[(.+)\]:(\d+)$/);
|
||||
if (bracketedMatch?.[1]) {
|
||||
return bracketedMatch[1].toLowerCase();
|
||||
}
|
||||
|
||||
const ipv4WithPortMatch = first.match(/^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/);
|
||||
if (ipv4WithPortMatch?.[1]) {
|
||||
return ipv4WithPortMatch[1];
|
||||
}
|
||||
|
||||
return first.toLowerCase();
|
||||
}
|
||||
|
||||
function resolveSourceIp(request: Request) {
|
||||
return (
|
||||
request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ??
|
||||
return sanitizeIpAddress(
|
||||
request.headers.get("x-forwarded-for") ??
|
||||
request.headers.get("x-real-ip") ??
|
||||
request.headers.get("cf-connecting-ip") ??
|
||||
request.headers.get("true-client-ip") ??
|
||||
request.headers.get("x-client-ip") ??
|
||||
"unknown"
|
||||
);
|
||||
}
|
||||
|
||||
function isPrivateIp(ip: string | undefined) {
|
||||
if (!ip) return false;
|
||||
if (!ip) return true;
|
||||
const normalized = ip.trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
if (normalized === "::1" || normalized === "localhost") {
|
||||
if (normalized === "unknown" || normalized === "localhost") {
|
||||
return true;
|
||||
}
|
||||
if (normalized === "::1" || normalized.startsWith("fe80")) {
|
||||
return true;
|
||||
}
|
||||
if (isIP(normalized) === 6 && (normalized.startsWith("fc") || normalized.startsWith("fd"))) {
|
||||
return true;
|
||||
}
|
||||
if (isIP(normalized) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -68,10 +104,6 @@ function isPrivateIp(ip: string | undefined) {
|
||||
return secondOctet >= 16 && secondOctet <= 31;
|
||||
}
|
||||
|
||||
if (normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe80")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,11 +82,42 @@ export async function GET(request: NextRequest) {
|
||||
actorSummaryRows.map((row) => [row.actorType, row._count._all])
|
||||
);
|
||||
|
||||
const isInternalActorId = (actorId: string | null | undefined) => {
|
||||
if (!actorId) return true;
|
||||
const normalized = actorId.toLowerCase();
|
||||
|
||||
if (normalized === "unknown" || normalized === "mcp-anonymous") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const ipMatch = normalized.match(/^open-tasks:([a-z0-9.:_-]+)$/);
|
||||
if (!ipMatch?.[1]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const actorIp = ipMatch[1];
|
||||
if (actorIp.startsWith("127.") || actorIp.startsWith("10.") || actorIp.startsWith("192.168.")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (actorIp.startsWith("172.")) {
|
||||
const secondOctet = Number(actorIp.split(".")[1]);
|
||||
return secondOctet >= 16 && secondOctet <= 31;
|
||||
}
|
||||
|
||||
if (actorIp === "localhost" || actorIp === "unknown" || actorIp.startsWith("fc") || actorIp.startsWith("fd")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const externalActorSummary = externalActorRows
|
||||
.map((row) => ({
|
||||
actorId: row.actorId || "unknown",
|
||||
events: row._count._all,
|
||||
}))
|
||||
.filter((row) => !isInternalActorId(row.actorId))
|
||||
.sort((a, b) => b.events - a.events)
|
||||
.slice(0, 20);
|
||||
|
||||
@@ -127,7 +158,7 @@ export async function GET(request: NextRequest) {
|
||||
});
|
||||
|
||||
const recentExternalEvents = recentEvents.filter((event) =>
|
||||
event.action.startsWith("EXTERNAL_")
|
||||
event.action.startsWith("EXTERNAL_") && !isInternalActorId(event.actorId)
|
||||
);
|
||||
|
||||
const recentInternalEvents = recentEvents.filter(
|
||||
|
||||
Reference in New Issue
Block a user