BGP security report

GET /api/v1/reports/bgp-security

GET /api/v1/reports/bgp-security?as_id=<uuid>
GET /api/v1/reports/bgp-security?as_id=<uuid>&at=<RFC3339>&af=ipv4&limit=200

The security detection report: three deterministic checks over the candidate lane — no baselining, no warm-up. Reads live bgp_rib_entry when at is absent or within ±60s of server time (refusing with available:false when no enabled BMP target feeds the scope), and bgp_rib_entry_history open at at behind the scoped full-RIB gate otherwise.

Auth: Any authenticated user.

Query parameters: as_id (required); optional routing_domain_id, device_id, af (ipv4|ipv6), at (RFC 3339, default now), limit (per-detector finding cap, default 100, max 500 — truncated reports a hit).

Response: 200 OK

{
  "at": "2026-08-18T00:00:00Z",
  "own_as": 65001,
  "scope": "presence",
  "data_source": "live",
  "available": true,
  "moas": [
    { "prefix": "203.0.113.0/24", "origins": [
      { "origin_as": 65100, "session_count": 1, "sessions": [ { "device_id": "uuid", "peer_ip": "198.51.100.31", "address_family": "ipv4", "peer_as": 64801 } ] },
      { "origin_as": 65200, "session_count": 2, "sessions": [ { "device_id": "uuid", "peer_ip": "198.51.100.32", "address_family": "ipv4", "peer_as": 64802 } ] } ] }
  ],
  "more_specifics": [
    { "prefix": "10.1.0.0/16", "origin_as": 65300, "covering_prefix": "10.0.0.0/8", "covering_origin": 65100,
      "session_count": 1, "sessions": [ { "device_id": "uuid", "peer_ip": "198.51.100.32", "address_family": "ipv4", "peer_as": 64802 } ] }
  ],
  "own_as_offers": [
    { "prefix": "203.0.113.0/24", "as_path": "64802 65001 65400",
      "session": { "device_id": "uuid", "peer_ip": "198.51.100.32", "address_family": "ipv4", "peer_as": 64802 } }
  ],
  "truncated": false
}

Claim discipline (enforced server-side): scope is always presence — every finding means "this path was offered on this session at T", never acceptance and never "hijack"; every finding carries its vantage sessions (sessions is a capped sample of 3, session_count the true total). The three checks: moas = ≥2 distinct origins open for one prefix; more_specifics = a candidate open for P while a strictly covering P′ with a different origin is open; own_as_offers = a peer offered a path already containing the own AS — offered AND provably not selectable on this router (RFC 4271 §9.1.2), the strongest claim of the three. The own-AS token match cannot distinguish AS_SEQUENCE from AS_SET/confederation members — the raw as_path rides along so the reader can judge. available:false (uncovered history instant, or no enabled target on the live lane) makes emptiness a coverage statement, not an all-clear. Errors: 400 on a missing as_id or invalid af/at.