fix: correct ASPA RFC citation and broken submission instructions

Reapplied against the current main (post src/features/ refactor),
since the old aspa/ module was carried over unchanged and still had
both bugs. Also caught two spots the stale local clone never had:
public/index.html's title= attribute and the newer index-classic.html.

ASPA has no RFC yet, still draft-ietf-sidrops-aspa-profile (object
format) and draft-ietf-sidrops-aspa-verification (path validation),
both in WG Last Call. "RFC 9582" is the ROA profile RFC, unrelated.

The generated "submit via RIPE DB webupdates / auto-dbm@ripe.net"
instructions were wrong too. ASPA objects are signed RPKI objects
created through a RIR's hosted RPKI platform, not RPSL text pasted
into the whois database. Replaced generateASPAObject/
generateRipeDbTemplate with summarizeASPAObject (review summary) and
generateASPASubmissionGuide (per-RIR: status, where to actually
create the object, source link). aspa_generate now takes an optional
rir param instead of a RIPE-specific maintainer handle.

Also corrected an overprecise APNIC launch date: APNIC's own blog
doesn't give one, only a vague "since Nov 2025" for all three live
RIRs collectively, which doesn't match RIPE's and ARIN's own more
precise announcements.
This commit is contained in:
Rene Fichtmueller 2026-07-15 23:53:00 +02:00
parent aa43c68554
commit 5c1cb1652b
12 changed files with 177 additions and 142 deletions

View File

@ -511,7 +511,7 @@ a{color:var(--blue);text-decoration:none;transition:color .2s}a:hover{color:var(
<div style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">
<div style="width:36px;height:36px;border-radius:8px;background:rgba(255,158,100,.12);border:1px solid rgba(255,158,100,.2);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:1rem">🔐</div>
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange)">ASPA (RFC 9582)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem">AS Provider Authorization from Cloudflare RPKI JSON feed. RFC-compliant upstream/downstream path verification with valley detection.</div><a href="https://www.ietf.org/archive/id/draft-ietf-sidrops-aspa-verification-14.html" target="_blank" style="font-size:.65rem;color:var(--blue)">IETF Draft-14</a></div>
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange)">ASPA (IETF draft, not yet an RFC)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem">AS Provider Authorization from Cloudflare RPKI JSON feed. Draft-compliant upstream/downstream path verification with valley detection.</div><a href="https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/" target="_blank" style="font-size:.65rem;color:var(--blue)">IETF Draft</a></div>
</div>
<div style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">

View File

@ -733,7 +733,7 @@ a:hover{text-decoration:underline}
<div class="verdict-body">
<div class="verdict-label">ASPA</div>
<div class="verdict-value valid">DEPLOYED</div>
<div class="verdict-sub">RFC 9582 — provider set complete</div>
<div class="verdict-sub">ASPA draft, provider set complete</div>
</div>
</div>
@ -880,7 +880,7 @@ a:hover{text-decoration:underline}
<div class="health-mark pass"></div>
<div>
<div class="health-text">ASPA record present</div>
<div class="health-sub">RFC 9582 provider authorisation</div>
<div class="health-sub">ASPA draft provider authorisation</div>
</div>
</div>
<div class="health-item">

View File

@ -554,7 +554,7 @@ a{color:var(--blue);text-decoration:none;transition:color .2s}a:hover{color:var(
<div style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">
<div style="width:36px;height:36px;border-radius:8px;background:rgba(255,158,100,.12);border:1px solid rgba(255,158,100,.2);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:1rem">🔐</div>
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange)">ASPA (RFC 9582)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem">AS Provider Authorization from Cloudflare RPKI JSON feed. RFC-compliant upstream/downstream path verification with valley detection.</div><a href="https://www.ietf.org/archive/id/draft-ietf-sidrops-aspa-verification-14.html" target="_blank" style="font-size:.65rem;color:var(--blue)">IETF Draft-14</a></div>
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange)">ASPA (IETF draft, not yet an RFC)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem">AS Provider Authorization from Cloudflare RPKI JSON feed. Draft-compliant upstream/downstream path verification with valley detection.</div><a href="https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/" target="_blank" style="font-size:.65rem;color:var(--blue)">IETF Draft</a></div>
</div>
<div style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">

View File

@ -581,7 +581,7 @@ a:hover{color:var(--purple)}
<div style="background:var(--bg);border:1px solid var(--border);border-radius:0;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">
<div style="width:36px;height:36px;border-radius:0;background:rgba(180,83,9,.08);border:1px solid rgba(180,83,9,.2);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:1rem">🔐</div>
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange);font-family:var(--body)">ASPA (RFC 9582)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem;font-family:var(--body)">AS Provider Authorization from Cloudflare RPKI JSON feed. RFC-compliant upstream/downstream path verification with valley detection.</div><a href="https://www.ietf.org/archive/id/draft-ietf-sidrops-aspa-verification-14.html" target="_blank" style="font-size:.65rem;color:var(--blue);font-family:var(--mono)">IETF Draft-14</a></div>
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange);font-family:var(--body)">ASPA (IETF draft, not yet an RFC)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem;font-family:var(--body)">AS Provider Authorization from Cloudflare RPKI JSON feed. Draft-compliant upstream/downstream path verification with valley detection.</div><a href="https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/" target="_blank" style="font-size:.65rem;color:var(--blue);font-family:var(--mono)">IETF Draft</a></div>
</div>
<div style="background:var(--bg);border:1px solid var(--border);border-radius:0;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">

View File

@ -558,7 +558,7 @@ body.dark .card{border-top-color:#e8e4dc}
</section>
<!-- ASPA Status -->
<section class="card" id="aspaCard" title="ASPA (Autonomous System Provider Authorization) status — RFC 9582. Verifies whether this AS has published which providers are authorized to forward its routes, protecting against route leaks">
<section class="card" id="aspaCard" title="ASPA (Autonomous System Provider Authorization) status. Still an IETF draft (draft-ietf-sidrops-aspa-profile), not yet an RFC. Verifies whether this AS has published which providers are authorized to forward its routes, protecting against route leaks">
<div class="card-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2v-4M9 21H5a2 2 0 0 1-2-2v-4"/></svg>
ASPA Status
@ -815,7 +815,7 @@ body.dark .card{border-top-color:#e8e4dc}
<div style="background:var(--bg);border:1px solid var(--border);border-radius:0;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">
<div style="width:36px;height:36px;border-radius:0;background:rgba(180,83,9,.08);border:1px solid rgba(180,83,9,.2);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:1rem">🔐</div>
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange);font-family:var(--body)">ASPA (RFC 9582)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem;font-family:var(--body)">AS Provider Authorization from Cloudflare RPKI JSON feed. RFC-compliant upstream/downstream path verification with valley detection.</div><a href="https://www.ietf.org/archive/id/draft-ietf-sidrops-aspa-verification-14.html" target="_blank" style="font-size:.65rem;color:var(--blue);font-family:var(--mono)">IETF Draft-14</a></div>
<div><div style="font-weight:700;font-size:.85rem;color:var(--orange);font-family:var(--body)">ASPA (IETF draft, not yet an RFC)</div><div style="font-size:.7rem;color:var(--muted);margin-top:.15rem;font-family:var(--body)">AS Provider Authorization from Cloudflare RPKI JSON feed. Draft-compliant upstream/downstream path verification with valley detection.</div><a href="https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/" target="_blank" style="font-size:.65rem;color:var(--blue);font-family:var(--mono)">IETF Draft</a></div>
</div>
<div style="background:var(--bg);border:1px solid var(--border);border-radius:0;padding:1rem;display:flex;gap:.75rem;align-items:flex-start">

View File

@ -6,7 +6,7 @@
* and individual networks. Useful for tracking the rollout of ASPA
* and identifying adoption gaps.
*
* @see https://www.rfc-editor.org/rfc/rfc9582
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-profile/ (object format, still an IETF draft, not yet an RFC)
*
* @example
* ```typescript

View File

@ -1,11 +1,16 @@
/**
* @module aspa/generator
* Auto-generate ASPA objects from BGP data.
* Detect upstream providers from BGP data and produce an ASPA submission guide.
*
* Analyzes BGP path data to detect upstream provider relationships,
* then generates ASPA objects in RIPE DB format for registration.
* Analyzes BGP path data to detect upstream provider relationships, then
* produces a plain-language provider list plus per-RIR instructions for
* creating the actual ASPA object. Real ASPA objects are signed RPKI/CMS
* objects created through your RIR's hosted RPKI platform (or a delegated
* RPKI CA); they are not RPSL text and cannot be submitted through the
* RIPE Database (whois) the way an aut-num or route object can. There is
* no `aspa:` or `upstream:` attribute in the RIPE DB schema.
*
* @see https://www.rfc-editor.org/rfc/rfc9582
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-profile/ (object format, still an IETF draft, not yet an RFC)
*
* @example
* ```typescript
@ -13,9 +18,9 @@
* console.log(providers);
* // [{ asn: 174, name: "Cogent", confidence: 0.95, pathCount: 42 }]
*
* const template = generateRipeDbTemplate(64501, providers, "MNT-EXAMPLE");
* console.log(template);
* // Ready-to-paste RIPE DB ASPA object
* const guide = generateASPASubmissionGuide(64501, providers);
* console.log(guide);
* // Provider list + a link to the RIR's actual RPKI dashboard
* ```
*/
@ -138,56 +143,56 @@ export function detectProviders(
});
}
// ── ASPA Object Generation ──────────────────────────────
// ── ASPA Provider Summary ────────────────────────────────
/**
* Generate an ASPA object in RPSL text format.
* Summarize detected providers in plain language, in the ASPA object's
* logical field order (customerASID, then providers ascending by ASN,
* per draft-ietf-sidrops-aspa-profile).
*
* Produces a human-readable ASPA object suitable for display or
* manual registration. Includes comments explaining each field.
* This is a human-readable summary for review, not a submittable object.
* There is no text format you can paste into a registry to create an
* ASPA object; it must be created through your RIR's RPKI platform.
*
* @param asn - The customer ASN
* @param providers - Detected upstream providers
* @returns RPSL-formatted ASPA object text
* @returns Plain-language summary of the ASPA object's intended content
*
* @example
* ```typescript
* const text = generateASPAObject(64501, [
* const text = summarizeASPAObject(64501, [
* { asn: 174, name: "Cogent", confidence: 0.95, pathCount: 42, afi: ["ipv4", "ipv6"] },
* ]);
* console.log(text);
* // aut-num: AS64501
* // aspa: AS64501
* // upstream: AS174 # Cogent (confidence: 95%, seen in 42 paths)
* // ...
* // Customer AS64501 would authorize: AS174 (Cogent, confidence 95%, seen in 42 paths)
* ```
*/
export function generateASPAObject(
export function summarizeASPAObject(
asn: number,
providers: ReadonlyArray<Provider>
): string {
const lines: string[] = [
`% ASPA object for AS${asn}`,
`% Generated by PeerCortex on ${new Date().toISOString()}`,
`% Based on BGP path analysis — review before submitting to your RIR`,
`%`,
`% ASPA (Autonomous System Provider Authorization) declares which ASNs`,
`% are authorized upstream providers of this AS. This helps prevent`,
`% route leaks by allowing RPKI validators to verify AS path legitimacy.`,
`%`,
`% Reference: RFC 9582 — Autonomous System Provider Authorization`,
`# ASPA summary for AS${asn}`,
`# Generated by PeerCortex on ${new Date().toISOString()}`,
`# Based on BGP path analysis. Review before creating the real object.`,
`#`,
`# ASPA (Autonomous System Provider Authorization) declares which ASNs`,
`# are authorized upstream providers of this AS. This helps prevent`,
`# route leaks by allowing RPKI validators to verify AS path legitimacy.`,
`#`,
`# Reference: IETF draft-ietf-sidrops-aspa-profile (not yet an RFC).`,
`# This is a summary for review only. It is NOT a submittable object.`,
`# ASPA objects are signed RPKI objects created through your RIR's`,
`# hosted RPKI platform, not RPSL text you paste into a registry.`,
``,
`aut-num: AS${asn}`,
`aspa: AS${asn}`,
`Customer: AS${asn}`,
];
for (const provider of providers) {
const sortedProviders = [...providers].sort((a, b) => a.asn - b.asn);
for (const provider of sortedProviders) {
const afiStr = provider.afi.join(", ");
const comment = `# ${provider.name} (confidence: ${Math.round(provider.confidence * 100)}%, seen in ${provider.pathCount} paths)`;
lines.push(`upstream: AS${provider.asn} ${comment}`);
if (provider.afi.length === 1) {
lines.push(` afi: ${afiStr}`);
}
const comment = `${provider.name} (confidence: ${Math.round(provider.confidence * 100)}%, seen in ${provider.pathCount} paths, ${afiStr})`;
lines.push(` Provider: AS${provider.asn} # ${comment}`);
}
lines.push(``);
@ -195,80 +200,105 @@ export function generateASPAObject(
return lines.join("\n");
}
/** Which Regional Internet Registry an ASN belongs to, for RIR-specific guidance */
export type RIR = "ripe" | "arin" | "apnic" | "lacnic" | "afrinic";
/** Per-RIR ASPA production status and the correct place to create the object */
const RIR_ASPA_INFO: Record<
RIR,
{ readonly status: string; readonly howTo: string; readonly infoUrl: string }
> = {
ripe: {
status: "Production since 15 Dec 2025",
howTo: "RIPE NCC LIR Portal, RPKI Dashboard, ASPA section",
infoUrl: "https://www.ripe.net/manage-ips-and-asns/resource-management/rpki/aspa/",
},
arin: {
status: "Production since 20 Jan 2026",
howTo: "ARIN Online, Routing Security menu, ASPA section",
infoUrl: "https://www.arin.net/resources/manage/rpki/aspa/",
},
apnic: {
status: "Production (exact launch date not published by APNIC; blog post from 9 Jul 2026 confirms support is live)",
howTo: "MyAPNIC or the APNIC Registry API",
infoUrl: "https://help.apnic.net/s/article/ASPA",
},
lacnic: {
status: "Not yet in production. Committed for end of 2026",
howTo: "Not yet available",
infoUrl: "https://blog.lacnic.net/en/programa-rpki-nro-resumen/",
},
afrinic: {
status: "Not yet in production, no committed date",
howTo: "Not yet available",
infoUrl: "https://blog.afrinic.net/nro-rpki-program-2025-in-review",
},
};
/**
* Generate a complete RIPE DB template ready for submission.
* Produce a submission guide for an ASN's detected providers.
*
* Produces a full RPSL object including maintainer, source, and
* administrative fields required for RIPE DB submission.
* This does NOT produce a pasteable object, because ASPA objects are
* signed RPKI objects, not RPSL text. It produces the provider list to
* review plus the correct place to actually create the object for the
* given RIR.
*
* @param asn - The customer ASN
* @param providers - Detected upstream providers
* @param maintainer - RIPE DB maintainer handle (e.g., "MNT-EXAMPLE")
* @returns Complete RIPE DB template text
* @param rir - Which RIR this ASN is registered with
* @returns Human-readable guide: provider list plus RIR-specific instructions
*
* @example
* ```typescript
* const template = generateRipeDbTemplate(
* const guide = generateASPASubmissionGuide(
* 13335,
* [{ asn: 174, name: "Cogent", confidence: 0.95, pathCount: 100, afi: ["ipv4", "ipv6"] }],
* "MNT-CLOUDFLARE"
* "ripe"
* );
* // Paste this into https://apps.db.ripe.net/db-web-ui/webupdates
* ```
*/
export function generateRipeDbTemplate(
export function generateASPASubmissionGuide(
asn: number,
providers: ReadonlyArray<Provider>,
maintainer: string
rir: RIR
): string {
const info = RIR_ASPA_INFO[rir];
const lines: string[] = [
`% ============================================================`,
`% ASPA Object Template for AS${asn}`,
`% Generated by PeerCortex — ${new Date().toISOString()}`,
`% ============================================================`,
`%`,
`% INSTRUCTIONS:`,
`% 1. Review the provider list below for accuracy`,
`% 2. Remove any providers you no longer use`,
`% 3. Add any providers that were not detected`,
`% 4. Submit via: https://apps.db.ripe.net/db-web-ui/webupdates`,
`% 5. Or via email to auto-dbm@ripe.net`,
`%`,
`% NOTE: ASPA objects are part of the RPKI framework.`,
`% Your RIR must support ASPA object creation.`,
`% Check with your RIR for current ASPA support status.`,
`%`,
`# ============================================================`,
`# ASPA Submission Guide for AS${asn}`,
`# Generated by PeerCortex, ${new Date().toISOString()}`,
`# ============================================================`,
`#`,
`# ASPA support at your RIR (${rir.toUpperCase()}): ${info.status}`,
`# Where to create the object: ${info.howTo}`,
`# More info: ${info.infoUrl}`,
`#`,
`# ASPA objects are signed RPKI objects. There is no text format`,
`# to paste into a registry database. Use the RIR platform above`,
`# and enter the provider ASNs listed below.`,
`#`,
``,
`Customer: AS${asn}`,
];
// Build the main object
lines.push(`aut-num: AS${asn}`);
for (const provider of providers) {
// Only include high-confidence providers in the template
if (provider.confidence >= 0.5) {
const afiComment =
provider.afi.length === 2
? ""
: ` # ${provider.afi[0]} only`;
lines.push(
`upstream: AS${provider.asn}${afiComment}`
);
}
const highConfidence = providers.filter((p) => p.confidence >= 0.5);
const sortedHigh = [...highConfidence].sort((a, b) => a.asn - b.asn);
for (const provider of sortedHigh) {
const afiComment =
provider.afi.length === 2 ? "" : ` # ${provider.afi[0]} only`;
lines.push(` Provider to add: AS${provider.asn}${afiComment}`);
}
lines.push(`mnt-by: ${maintainer}`);
lines.push(`source: RIPE`);
lines.push(``);
// Add low-confidence providers as comments
const lowConfidence = providers.filter((p) => p.confidence < 0.5);
if (lowConfidence.length > 0) {
lines.push(`% The following providers were detected with low confidence.`);
lines.push(`% Uncomment and add them if they are legitimate providers:`);
for (const provider of lowConfidence) {
lines.push(`# The following providers were detected with low confidence.`);
lines.push(`# Review before adding them:`);
const sortedLow = [...lowConfidence].sort((a, b) => a.asn - b.asn);
for (const provider of sortedLow) {
lines.push(
`% upstream: AS${provider.asn} # ${provider.name} (confidence: ${Math.round(provider.confidence * 100)}%)`
`# AS${provider.asn} (${provider.name}, confidence: ${Math.round(provider.confidence * 100)}%)`
);
}
lines.push(``);

View File

@ -6,8 +6,8 @@
* and detect route leaks in real time. Combines ASPA validation
* with heuristic analysis for comprehensive leak detection.
*
* @see https://www.rfc-editor.org/rfc/rfc9582
* @see https://www.rfc-editor.org/rfc/rfc7908 — Route Leak Problem Definition
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/ (path validation, still an IETF draft, not yet an RFC)
* @see https://www.rfc-editor.org/rfc/rfc7908 (Route Leak Problem Definition)
*
* @example
* ```typescript

View File

@ -5,7 +5,7 @@
* Provides functions to retrieve ASPA objects from the RIPE Database
* and maintain an in-memory cache with TTL-based expiration.
*
* @see https://www.ripe-editor.org/rfc/rfc9582
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-profile/ (object format, still an IETF draft, not yet an RFC)
* @see https://apps.db.ripe.net/docs/DatabaseReference/RIPE-Database-Structure/
*
* @example

View File

@ -7,7 +7,7 @@
* if AS13335 had deployed it?" or "What is the aggregate prevention rate
* across all incidents in the last 30 days?"
*
* @see https://www.rfc-editor.org/rfc/rfc9582
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/ (path validation, still an IETF draft, not yet an RFC)
*
* @example
* ```typescript

View File

@ -1,13 +1,13 @@
/**
* @module aspa/validator
* RFC 9582 Section 6 ASPA-based AS path validation algorithm.
* draft-ietf-sidrops-aspa-verification ASPA-based AS path validation algorithm.
*
* Implements the Autonomous System Provider Authorization (ASPA) path
* validation procedure as defined in RFC 9582. ASPA enables detection
* validation procedure as defined in draft-ietf-sidrops-aspa-verification. ASPA enables detection
* of route leaks and unauthorized path segments by verifying that each
* AS in a BGP path has authorized its upstream provider relationship.
*
* @see https://www.rfc-editor.org/rfc/rfc9582#section-6
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/ (still an IETF draft, not yet an RFC)
*
* @example
* ```typescript
@ -34,7 +34,7 @@
* Maps a customer AS to its authorized upstream providers,
* optionally scoped to specific address families.
*
* @see RFC 9582 Section 3 ASPA Profile
* @see draft-ietf-sidrops-aspa-profile Section 3 (object format, still an IETF draft)
*/
export interface ASPAObject {
/** The customer AS that created this authorization */
@ -53,7 +53,7 @@ export interface ASPAObject {
* found, and whether a route leak was detected.
*/
export interface ASPAValidationResult {
/** Overall validation status per RFC 9582 Section 6 */
/** Overall validation status per draft-ietf-sidrops-aspa-verification */
readonly status: "valid" | "invalid" | "unknown" | "unverifiable";
/** The AS path that was validated */
readonly path: ReadonlyArray<number>;
@ -98,7 +98,7 @@ export interface ASPAViolation {
* @returns "provider" if authorized, "not-provider" if explicitly not listed,
* or "no-attestation" if the customer has no ASPA object
*
* @see RFC 9582 Section 6 Verification of Provider Authorization
* @see draft-ietf-sidrops-aspa-verification Verification of Provider Authorization
*/
function checkProviderAuthorization(
customerAsn: number,
@ -135,13 +135,13 @@ function deduplicatePath(path: ReadonlyArray<number>): ReadonlyArray<number> {
// ── Core Validation Functions ───────────────────────────
/**
* Validate an AS path in the upstream direction per RFC 9582 Section 6.
* Validate an AS path in the upstream direction per draft-ietf-sidrops-aspa-verification.
*
* Walks the path from the origin AS (rightmost) toward the validating AS
* (leftmost). For each pair (customer, provider), verifies that the
* customer has authorized the provider via an ASPA object.
*
* The upstream validation procedure (RFC 9582 Section 6):
* The upstream validation procedure (draft-ietf-sidrops-aspa-verification):
* - If the path has 0 or 1 unique ASNs, the result is "valid".
* - Walk from index N-1 (origin) toward index 0.
* - At each hop, check if path[i] authorizes path[i-1] as its provider.
@ -209,7 +209,7 @@ export function validateUpstream(
const confidence = totalHops > 0 ? coveredHops / totalHops : 1.0;
// Determine overall status per RFC 9582 Section 6
// Determine overall status per draft-ietf-sidrops-aspa-verification
if (violations.length > 0) {
const leakingViolation = violations[0];
return {
@ -242,13 +242,13 @@ export function validateUpstream(
}
/**
* Validate an AS path in the downstream direction per RFC 9582 Section 6.
* Validate an AS path in the downstream direction per draft-ietf-sidrops-aspa-verification.
*
* Reverses the path and applies the upstream validation procedure.
* Downstream validation is used when the validating AS is receiving
* a route from a customer rather than a provider.
*
* Per RFC 9582, the downstream verification is the mirror image of upstream:
* Per draft-ietf-sidrops-aspa-verification, the downstream verification is the mirror image of upstream:
* - Reverse the path so the "origin" from the downstream perspective is leftmost.
* - Apply the same provider-authorization checks.
*
@ -295,7 +295,7 @@ export function validateDownstream(
* @returns Full validation result including status, violations, leak
* detection, and confidence score
*
* @see RFC 9582 Section 6 Procedure for Verifying the AS_PATH Attribute
* @see draft-ietf-sidrops-aspa-verification Procedure for Verifying the AS_PATH Attribute
*
* @example
* ```typescript

View File

@ -5,7 +5,8 @@
* Exposes ASPA validation, analysis, generation, simulation, coverage,
* and leak detection capabilities through the Model Context Protocol.
*
* @see https://www.rfc-editor.org/rfc/rfc9582
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-profile/ (object format, still an IETF draft, not yet an RFC)
* @see https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/ (path validation, still an IETF draft, not yet an RFC)
*/
import { z } from "zod";
@ -13,8 +14,8 @@ import type { ASPAValidationResult } from "../../aspa/validator.js";
import { validatePath } from "../../aspa/validator.js";
import type { ASPAObject } from "../../aspa/validator.js";
import { fetchASPAObjects } from "../../aspa/objects.js";
import { detectProviders, generateASPAObject, generateRipeDbTemplate } from "../../aspa/generator.js";
import type { BGPPath } from "../../aspa/generator.js";
import { detectProviders, summarizeASPAObject, generateASPASubmissionGuide } from "../../aspa/generator.js";
import type { BGPPath, RIR } from "../../aspa/generator.js";
import { simulateASPADeployment } from "../../aspa/simulator.js";
import type { BGPIncident } from "../../aspa/simulator.js";
import { getASPACoverage, getASPACoverageByRegion, compareASPAAdoption } from "../../aspa/coverage.js";
@ -60,20 +61,22 @@ export const aspaAnalyzeSchema = z.object({
*
* @example
* ```
* > Generate an ASPA object for AS13335
* > Generate an ASPA submission guide for AS13335, registered with RIPE
*
* Returns: RIPE DB-ready ASPA object template with detected
* upstream providers (AS174, AS3356, etc.) and submission instructions.
* Returns: detected upstream providers (AS174, AS3356, etc.) plus
* RIR-specific instructions for creating the actual ASPA object.
* ```
*/
export const aspaGenerateSchema = z.object({
asn: z
.union([z.string(), z.number()])
.describe("ASN to generate ASPA object for"),
maintainer: z
.string()
.describe("ASN to generate an ASPA submission guide for"),
rir: z
.enum(["ripe", "arin", "apnic", "lacnic", "afrinic"])
.optional()
.describe("RIPE DB maintainer handle (e.g., 'MNT-CLOUDFLARE')"),
.describe(
"The RIR this ASN is registered with. If omitted, guidance for all five RIRs is returned so the caller can pick the right one."
),
});
/**
@ -143,7 +146,7 @@ export const aspaLeaksSchema = z.object({
* Validate an AS path against ASPA objects.
*
* Fetches ASPA objects for all ASNs in the path, then runs the
* RFC 9582 Section 6 validation algorithm.
* validation algorithm from draft-ietf-sidrops-aspa-verification.
*
* @example
* ```
@ -205,7 +208,7 @@ export async function handleASPAValidate(
* ],
* "recommendations": [
* "Register ASPA object listing AS174 and AS3356 as providers",
* "Submit via RIPE DB at https://apps.db.ripe.net/db-web-ui/webupdates"
* "ASPA objects are created through your RIR's hosted RPKI platform, not the RIPE Database"
* ]
* }
* ```
@ -263,10 +266,10 @@ export async function handleASPAAnalyze(
`Register an ASPA object for AS${asn} with your RIR to enable route leak prevention.`
);
recommendations.push(
`Submit via RIPE DB at https://apps.db.ripe.net/db-web-ui/webupdates`
`ASPA objects are created through your RIR's hosted RPKI platform (e.g. the RIPE NCC RPKI Dashboard, ARIN Online's Routing Security section, or MyAPNIC), not through the RIPE Database (whois).`
);
recommendations.push(
`Use the peercortex_aspa_generate tool to create a ready-to-submit template.`
`Use the peercortex_aspa_generate tool with your RIR to get the detected provider list and the correct place to submit it.`
);
} else {
recommendations.push(
@ -294,22 +297,28 @@ export async function handleASPAAnalyze(
};
}
const RIR_LIST: ReadonlyArray<RIR> = ["ripe", "arin", "apnic", "lacnic", "afrinic"];
/**
* Generate an ASPA object template for an ASN.
* Generate an ASPA submission guide for an ASN.
*
* Detects upstream providers from BGP path data and generates
* a RIPE DB-ready ASPA object template.
* Detects upstream providers from BGP path data and produces a provider
* list plus the correct place to actually create the ASPA object at the
* given RIR. If no RIR is given, guidance for all five is returned.
* There is no RPSL text format that can be pasted into a registry to
* create an ASPA object; it is a signed RPKI object created through the
* RIR's own hosted RPKI platform.
*
* @example
* ```
* > Generate an ASPA object for AS13335
* > Generate an ASPA submission guide for AS13335, registered with RIPE
*
* Returns:
* {
* "asn": 13335,
* "template": "aut-num: AS13335\nupstream: AS174\nupstream: AS3356\nmnt-by: MNT-CLOUDFLARE\nsource: RIPE",
* "detectedProviders": [{ "asn": 174, ... }, { "asn": 3356, ... }],
* "instructions": "Submit via RIPE DB..."
* "summary": "Customer: AS13335\n Provider: AS174 ...",
* "guides": { "ripe": "..." },
* "detectedProviders": [{ "asn": 174, ... }, { "asn": 3356, ... }]
* }
* ```
*/
@ -317,17 +326,15 @@ export async function handleASPAGenerate(
input: z.infer<typeof aspaGenerateSchema>
): Promise<{
asn: number;
object: string;
template: string;
summary: string;
guides: Partial<Record<RIR, string>>;
detectedProviders: ReadonlyArray<{
asn: number;
name: string;
confidence: number;
}>;
instructions: string;
}> {
const asn = parseASN(input.asn);
const maintainer = input.maintainer ?? `MNT-AS${asn}`;
// Detect providers (in full implementation, fetch BGP paths from RIPE Stat)
// For now, try to infer from any existing ASPA objects
@ -354,25 +361,23 @@ export async function handleASPAGenerate(
// Continue with empty provider list
}
const object = generateASPAObject(asn, providers);
const template = generateRipeDbTemplate(asn, providers, maintainer);
const summary = summarizeASPAObject(asn, providers);
const rirsToGenerate = input.rir ? [input.rir] : RIR_LIST;
const guides: Partial<Record<RIR, string>> = {};
for (const rir of rirsToGenerate) {
guides[rir] = generateASPASubmissionGuide(asn, providers, rir);
}
return {
asn,
object,
template,
summary,
guides,
detectedProviders: providers.map((p) => ({
asn: p.asn,
name: p.name,
confidence: p.confidence,
})),
instructions:
`To register this ASPA object:\n` +
`1. Review the detected providers and adjust as needed\n` +
`2. Go to https://apps.db.ripe.net/db-web-ui/webupdates\n` +
`3. Paste the template and submit\n` +
`4. Alternatively, email the template to auto-dbm@ripe.net\n` +
`\nNote: Your RIR must support ASPA objects. Check current support status.`,
};
}