Catalyst / admin/Synapse-Sonar 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / Synapse-Sonar

public

Attack Surface Simulation

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
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 });
}