Rene Fichtmueller f2470f3e56 feat: initial release — AI-powered network intelligence platform
PeerCortex unifies PeeringDB, RIPE Stat, bgproutes.io, RIPE Atlas,
Route Views, IRR, RPKI, and CAIDA into a single AI-queryable MCP Server
for network engineers. Powered by local Ollama.

Core capabilities:
- 34 MCP tools for network intelligence
- 11 data sources unified
- ASPA validation engine (RFC 9582) with leak detection
- Peering partner discovery with AI-ranked matches
- BGP analysis and anomaly detection
- RPKI monitoring and compliance reports
- Latency/traceroute via RIPE Atlas
- Transit analysis and cost comparison
- IX traffic statistics
- AS topology mapping
- ASPA object generator and simulator
- 100% local AI — no cloud dependencies
2026-03-26 07:26:14 +13:00

323 lines
9.9 KiB
TypeScript

/**
* @module mcp-server/tools/security
* MCP Tool: BGP security — hijack detection, route leak analysis (ASPA-based),
* bogon filtering, and blacklist checks.
*
* Provides security-focused analysis of BGP routing using RPKI ROV and ASPA
* validation from bgproutes.io, combined with historical data from RIPE Stat
* and other sources.
*/
import { z } from "zod";
import type { ASN, AnomalySeverity } from "../../types/common.js";
// ── Tool Schemas ─────────────────────────────────────────
/** Input schema for hijack detection */
export const hijackDetectionSchema = z.object({
prefix: z
.string()
.describe("IP prefix to check for hijacks (e.g., '1.1.1.0/24')"),
expectedOriginAsn: z
.number()
.optional()
.describe("Expected origin ASN (if known)"),
includeHistory: z
.boolean()
.optional()
.default(true)
.describe("Include historical hijack events"),
});
/** Input schema for ASPA-based route leak detection */
export const routeLeakDetectionSchema = z.object({
asn: z
.union([z.string(), z.number()])
.describe("ASN to check for route leaks involving their prefixes"),
prefixes: z
.array(z.string())
.optional()
.describe("Specific prefixes to check (default: all announced by ASN)"),
timeRange: z
.object({
start: z.string().describe("Start time (ISO 8601)"),
end: z.string().describe("End time (ISO 8601)"),
})
.optional()
.describe("Time range to search for leaks"),
});
/** Input schema for bogon check */
export const bogonCheckSchema = z.object({
asn: z
.union([z.string(), z.number()])
.describe("ASN to check for bogon origination or path issues"),
includeReserved: z
.boolean()
.optional()
.default(true)
.describe("Check for reserved/unallocated prefix announcements"),
includeBogonAsns: z
.boolean()
.optional()
.default(true)
.describe("Check for bogon ASNs in AS paths"),
});
/** Input schema for blacklist check */
export const blacklistCheckSchema = z.object({
resource: z
.string()
.describe("IP address, prefix, or ASN to check against blacklists"),
lists: z
.array(z.string())
.optional()
.describe("Specific blacklists to check (default: all known)"),
});
// ── Result Types ─────────────────────────────────────────
/** Detected BGP hijack */
export interface HijackEvent {
readonly prefix: string;
readonly legitimateOrigin: ASN;
readonly hijackerAsn: ASN;
readonly hijackerName: string;
readonly type: "exact-match" | "sub-prefix" | "squat";
readonly severity: AnomalySeverity;
readonly rpkiStatus: "valid" | "invalid" | "not-found" | "unknown";
readonly firstSeen: string;
readonly lastSeen: string;
readonly affectedPaths: number;
readonly description: string;
}
/** Hijack detection result */
export interface HijackDetectionResult {
readonly prefix: string;
readonly expectedOriginAsn: ASN | null;
readonly currentOrigins: ReadonlyArray<{
readonly asn: ASN;
readonly name: string;
readonly rpkiValid: boolean;
}>;
readonly activeHijacks: ReadonlyArray<HijackEvent>;
readonly historicalHijacks: ReadonlyArray<HijackEvent>;
readonly riskLevel: AnomalySeverity;
readonly recommendations: ReadonlyArray<string>;
}
/** ASPA-based route leak event */
export interface RouteLeakEvent {
readonly prefix: string;
readonly leakerAsn: ASN;
readonly leakerName: string;
readonly leakedTo: ReadonlyArray<ASN>;
readonly aspaValidation: {
readonly state: "invalid";
readonly violatingHop: {
readonly asn: ASN;
readonly position: number;
readonly reason: string;
};
};
readonly severity: AnomalySeverity;
readonly firstSeen: string;
readonly lastSeen: string;
readonly description: string;
}
/** Route leak detection result */
export interface RouteLeakDetectionResult {
readonly asn: ASN;
readonly name: string;
readonly prefixesChecked: number;
readonly activeLeaks: ReadonlyArray<RouteLeakEvent>;
readonly historicalLeaks: ReadonlyArray<RouteLeakEvent>;
readonly aspaStatus: {
readonly hasAspaObjects: boolean;
readonly aspaObjectCount: number;
readonly coveragePercent: number;
};
readonly riskLevel: AnomalySeverity;
readonly recommendations: ReadonlyArray<string>;
}
/** Bogon check finding */
export interface BogonFinding {
readonly type: "bogon_prefix" | "bogon_asn" | "reserved_prefix" | "unallocated_prefix";
readonly resource: string;
readonly severity: AnomalySeverity;
readonly description: string;
readonly asPath: ReadonlyArray<ASN>;
}
/** Bogon check result */
export interface BogonCheckResult {
readonly asn: ASN;
readonly findings: ReadonlyArray<BogonFinding>;
readonly bogonPrefixCount: number;
readonly bogonAsnInPathCount: number;
readonly clean: boolean;
readonly recommendations: ReadonlyArray<string>;
}
/** Blacklist check result */
export interface BlacklistCheckResult {
readonly resource: string;
readonly listed: boolean;
readonly listings: ReadonlyArray<{
readonly list: string;
readonly listedSince: string;
readonly reason: string;
readonly delistUrl: string;
}>;
readonly clean: boolean;
}
// ── Tool Handlers ────────────────────────────────────────
/**
* Detect active and historical BGP hijacks for a prefix.
*
* Checks for unauthorized origin ASes, sub-prefix hijacks, and route
* squatting using RPKI ROV validation and MOAS conflict detection.
*
* @param input - Validated detection parameters
* @returns Hijack detection result with active/historical events
*
* @example
* ```
* > Check if 1.1.1.0/24 is being hijacked
*
* Returns: Current origins, RPKI validation, any MOAS conflicts,
* and historical hijack events.
* ```
*/
export async function handleHijackDetection(
input: z.infer<typeof hijackDetectionSchema>
): Promise<HijackDetectionResult> {
// TODO: Query bgproutes.io RIB for current origins of this prefix
// TODO: Validate each origin via RPKI ROV
// TODO: Compare with expected origin if provided
// TODO: Check for sub-prefix announcements (more-specific hijacks)
// TODO: Query historical data from RIPE Stat BGP updates
return {
prefix: input.prefix,
expectedOriginAsn: input.expectedOriginAsn ?? null,
currentOrigins: [],
activeHijacks: [],
historicalHijacks: [],
riskLevel: "info",
recommendations: [
"Create ROAs for all your prefixes to enable RPKI-based hijack protection",
"Monitor BGP announcements in real-time for early hijack detection",
"Register ASPA objects to protect against route leaks",
],
};
}
/**
* Detect route leaks using ASPA validation.
*
* Analyzes BGP paths for ASPA validation failures, which indicate
* potential route leaks where a customer or peer re-announces routes
* to unauthorized neighbors.
*
* ASPA (Autonomous System Provider Authorization) objects declare which
* ASes are authorized providers. A path that violates these declarations
* indicates a route leak.
*
* @param input - Validated detection parameters
* @returns Route leak detection result with ASPA analysis
*
* @example
* ```
* > Detect route leaks using ASPA validation for prefixes of AS13335
*
* Returns: Prefixes with ASPA-invalid paths, the leaking AS, severity,
* and recommendations for deploying ASPA objects.
* ```
*/
export async function handleRouteLeakDetection(
input: z.infer<typeof routeLeakDetectionSchema>
): Promise<RouteLeakDetectionResult> {
// TODO: Get all announced prefixes for the ASN (or use provided list)
// TODO: For each prefix, query bgproutes.io RIB entries with ASPA validation
// TODO: Filter for entries where aspaValidation.state === "invalid"
// TODO: Identify the leaking AS (the hop that violates ASPA)
// TODO: Query historical updates for past leak events
// TODO: Check if the ASN has registered ASPA objects
const _input = input;
return {
asn: 0, // TODO: parseASN(input.asn)
name: "",
prefixesChecked: 0,
activeLeaks: [],
historicalLeaks: [],
aspaStatus: {
hasAspaObjects: false,
aspaObjectCount: 0,
coveragePercent: 0,
},
riskLevel: "info",
recommendations: [
"Register ASPA objects to declare your authorized upstream providers",
"Monitor ASPA validation results for your prefixes in real-time",
"Coordinate with your upstreams to ensure they also deploy ASPA",
"Use bgproutes.io real-time stream for immediate leak alerting",
],
};
}
/**
* Check for bogon prefixes and bogon ASNs in routing.
*
* @param input - Validated check parameters
* @returns Bogon check result with findings
*/
export async function handleBogonCheck(
input: z.infer<typeof bogonCheckSchema>
): Promise<BogonCheckResult> {
// TODO: Get all announced prefixes for the ASN
// TODO: Check against IANA reserved/special-purpose ranges
// TODO: Check AS paths for bogon ASNs (reserved, documentation, private)
// TODO: Check for unallocated prefixes from RIR data
const _input = input;
return {
asn: 0,
findings: [],
bogonPrefixCount: 0,
bogonAsnInPathCount: 0,
clean: true,
recommendations: [],
};
}
/**
* Check a resource against known blacklists and reputation databases.
*
* @param input - Validated check parameters
* @returns Blacklist check result with listings
*/
export async function handleBlacklistCheck(
input: z.infer<typeof blacklistCheckSchema>
): Promise<BlacklistCheckResult> {
// TODO: Check against Spamhaus (DROP, EDROP, ASN-DROP)
// TODO: Check against Team Cymru bogon lists
// TODO: Check against various DNSBL services
// TODO: Aggregate results from specified or all lists
return {
resource: input.resource,
listed: false,
listings: [],
clean: true,
};
}