admin / Synapse-Sonar
publicAttack Surface Simulation
Synapse-Sonar / synapse-sonar / app / api / ingest / vulnerabilities / route.ts
2815 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | import { NextRequest, NextResponse } from "next/server"; import crypto from "crypto"; import { z } from "zod"; import { prisma } from "@/lib/prisma"; import { PayloadSchema, ingestFindings } from "@/lib/vulnIngest"; export const runtime = "nodejs"; /** * POST /api/ingest/vulnerabilities * * NetscanXi -> Synapse Sonar vulnerability feed (push model). * * Auth: Authorization: Bearer <ingest key> (the same NetscanXi "Sensor ingest * key" used for flow ingestion; resolves the tenant by SHA-256 hash). * * Body: * { * "findings": [ * { * "ip": "10.0.3.30", // required - maps to an Asset by IP * "hostname": "db-01", // optional * "cveId": "CVE-2024-1234", // required * "cvssScore": 9.8, // required (0.0 - 10.0) * "severity": "CRITICAL", // optional - derived from score if omitted * "title": "PostgreSQL RCE", // optional * "source": "NetscanXi v13", // optional (default "NETSCAN_XI") * "discoveredAt": "2026-06-30T10:00:00Z" // optional ISO-8601 * } * ] * } * * Assets are upserted by (tenant, IP) so findings for hosts not yet seen in flow * data still create nodes. Vulnerabilities are upserted by (asset, CVE). */ function extractBearer(req: NextRequest): string | null { const header = req.headers.get("authorization") ?? ""; const match = header.match(/^Bearer\s+(.+)$/i); return match ? match[1].trim() : null; } export async function POST(req: NextRequest) { // --- Authenticate & resolve tenant from the NetscanXi ingest key --- const apiKey = extractBearer(req); if (!apiKey) { return NextResponse.json({ error: "Missing bearer token" }, { status: 401 }); } const apiKeyHash = crypto.createHash("sha256").update(apiKey).digest("hex"); // Accept the dedicated vulnerability key OR the collector key (transition / // shared use). Generating a new collector key never revokes the vuln key. const keyRow = await prisma.$queryRaw<{ tenantId: string }[]>` SELECT "tenantId" FROM "integration_settings" WHERE provider = 'NETSCAN_XI' AND enabled = true AND (config->>'vulnKeyHash' = ${apiKeyHash} OR config->>'ingestKeyHash' = ${apiKeyHash}) LIMIT 1; `; const tenantId = keyRow[0]?.tenantId; if (!tenantId) { return NextResponse.json({ error: "Invalid API key" }, { status: 401 }); } // --- Validate payload --- let parsed; try { parsed = PayloadSchema.parse(await req.json()); } catch (err) { return NextResponse.json( { error: "Invalid payload", details: (err as z.ZodError).issues ?? String(err) }, { status: 422 } ); } const summary = await ingestFindings(tenantId, parsed.findings); return NextResponse.json({ ok: true, summary }); } |