fix(evaluations): correct fallback rounds + friendlier error messages
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
- Fallback names now use hyphens matching live API (carbanak-fin7, wizard-spider-sandworm) - Add APT3 (R1) and Enterprise 2025/er7 (R7) to fallback - verified from live API - Remove OilRig (R6) from fallback - CrowdStrike did not participate in Round 6 - Orange fallback banner only shows when NO rounds are available at all - Soft gray note when rounds are loaded but API had transient error - Check-new and import errors: detect 502/Cloudflare messages and show user-friendly text instead of raw Cloudflare HTML error messages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -64,10 +64,20 @@ _HEADERS = {
|
|||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Fallback: hardcoded public CrowdStrike ENTERPRISE rounds
|
# Fallback: hardcoded public CrowdStrike ENTERPRISE rounds
|
||||||
# Used when evals.mitre.org API is unreachable (Cloudflare, outage, etc.)
|
# Used when evals.mitre.org API is unreachable (Cloudflare 502, outage, etc.)
|
||||||
# These are well-known, publicly documented rounds — safe to hardcode.
|
#
|
||||||
|
# Names use the EXACT slugs the live API returns (hyphens, not underscores).
|
||||||
|
# Verified from live API response on 2025-06-05.
|
||||||
|
# CrowdStrike did NOT participate in Round 6 (OilRig) — not included.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
_FALLBACK_ROUNDS: list[dict[str, Any]] = [
|
_FALLBACK_ROUNDS: list[dict[str, Any]] = [
|
||||||
|
{
|
||||||
|
"name": "apt3",
|
||||||
|
"display_name": "APT3",
|
||||||
|
"eval_round": 1,
|
||||||
|
"domain": "ENTERPRISE",
|
||||||
|
"status": "PUBLIC",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "apt29",
|
"name": "apt29",
|
||||||
"display_name": "APT29",
|
"display_name": "APT29",
|
||||||
@@ -76,14 +86,14 @@ _FALLBACK_ROUNDS: list[dict[str, Any]] = [
|
|||||||
"status": "PUBLIC",
|
"status": "PUBLIC",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "carbanak_fin7",
|
"name": "carbanak-fin7",
|
||||||
"display_name": "Carbanak+FIN7",
|
"display_name": "Carbanak+FIN7",
|
||||||
"eval_round": 3,
|
"eval_round": 3,
|
||||||
"domain": "ENTERPRISE",
|
"domain": "ENTERPRISE",
|
||||||
"status": "PUBLIC",
|
"status": "PUBLIC",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "wizard_spider_sandworm",
|
"name": "wizard-spider-sandworm",
|
||||||
"display_name": "Wizard Spider + Sandworm",
|
"display_name": "Wizard Spider + Sandworm",
|
||||||
"eval_round": 4,
|
"eval_round": 4,
|
||||||
"domain": "ENTERPRISE",
|
"domain": "ENTERPRISE",
|
||||||
@@ -97,9 +107,9 @@ _FALLBACK_ROUNDS: list[dict[str, Any]] = [
|
|||||||
"status": "PUBLIC",
|
"status": "PUBLIC",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "oilrig",
|
"name": "er7",
|
||||||
"display_name": "OilRig",
|
"display_name": "Enterprise 2025",
|
||||||
"eval_round": 6,
|
"eval_round": 7,
|
||||||
"domain": "ENTERPRISE",
|
"domain": "ENTERPRISE",
|
||||||
"status": "PUBLIC",
|
"status": "PUBLIC",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -438,18 +438,24 @@ export default function SystemPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* API fallback warning */}
|
{/* API fallback warning — only when no rounds loaded at all */}
|
||||||
{!evalApiReachable && evalApiError && (
|
{!evalApiReachable && evalApiError && (!evalRounds || evalRounds.length === 0) && (
|
||||||
<div className="mb-4 flex items-start gap-3 rounded-lg border border-orange-500/30 bg-orange-900/10 p-3">
|
<div className="mb-4 flex items-start gap-3 rounded-lg border border-orange-500/30 bg-orange-900/10 p-3">
|
||||||
<AlertCircle className="h-4 w-4 text-orange-400 flex-shrink-0 mt-0.5" />
|
<AlertCircle className="h-4 w-4 text-orange-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="text-xs text-orange-300">
|
<div className="text-xs text-orange-300">
|
||||||
<span className="font-medium">Live API unavailable</span>
|
<span className="font-medium">MITRE Evaluations API temporarily unavailable</span>
|
||||||
{" — "}showing known public rounds (hardcoded). Importing will attempt to fetch
|
{" — "}could not load round list. The server may be overloaded. Try again later.
|
||||||
live results and may also fail if the API remains unreachable.
|
|
||||||
<span className="block mt-1 text-orange-400/70 font-mono truncate">{evalApiError}</span>
|
<span className="block mt-1 text-orange-400/70 font-mono truncate">{evalApiError}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* Soft note when using fallback data but rounds are still visible */}
|
||||||
|
{!evalApiReachable && evalRounds && evalRounds.length > 0 && (
|
||||||
|
<div className="mb-4 flex items-center gap-2 rounded-lg border border-gray-700 bg-gray-800/40 px-3 py-2 text-xs text-gray-400">
|
||||||
|
<AlertCircle className="h-3.5 w-3.5 text-gray-500 flex-shrink-0" />
|
||||||
|
Showing cached round list — live API had a transient error. Import may still work; retry if it fails.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* New round check result */}
|
{/* New round check result */}
|
||||||
{evalCheckResult && (
|
{evalCheckResult && (
|
||||||
@@ -461,9 +467,15 @@ export default function SystemPage() {
|
|||||||
: "border-gray-700 bg-gray-800/50"
|
: "border-gray-700 bg-gray-800/50"
|
||||||
}`}>
|
}`}>
|
||||||
{evalCheckResult.error ? (
|
{evalCheckResult.error ? (
|
||||||
<div className="flex items-center gap-2 text-sm text-red-400">
|
<div className="flex items-start gap-2 text-sm">
|
||||||
<XCircle className="h-4 w-4 flex-shrink-0" />
|
<XCircle className="h-4 w-4 flex-shrink-0 text-red-400 mt-0.5" />
|
||||||
<span>Check failed: {evalCheckResult.error}</span>
|
<div>
|
||||||
|
<span className="text-red-400">
|
||||||
|
{evalCheckResult.error.includes("502") || evalCheckResult.error.includes("Cloudflare") || evalCheckResult.error.includes("origin")
|
||||||
|
? "MITRE Evaluations API temporarily unavailable (server overload). Try again in a few minutes."
|
||||||
|
: `Check failed: ${evalCheckResult.error}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : evalCheckResult.new_round_available ? (
|
) : evalCheckResult.new_round_available ? (
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
@@ -514,12 +526,25 @@ export default function SystemPage() {
|
|||||||
{/* Import error */}
|
{/* Import error */}
|
||||||
{(importLatestMutation.isError || importRoundMutation.isError) && (
|
{(importLatestMutation.isError || importRoundMutation.isError) && (
|
||||||
<div className="mb-4 rounded-lg border border-red-500/30 bg-red-900/20 p-3">
|
<div className="mb-4 rounded-lg border border-red-500/30 bg-red-900/20 p-3">
|
||||||
<div className="flex items-center gap-2 text-sm text-red-400">
|
<div className="flex items-start gap-2 text-sm">
|
||||||
<XCircle className="h-4 w-4 flex-shrink-0" />
|
<XCircle className="h-4 w-4 flex-shrink-0 text-red-400 mt-0.5" />
|
||||||
<span>
|
<div>
|
||||||
{((importLatestMutation.error || importRoundMutation.error) as Error)?.message ??
|
{(() => {
|
||||||
"Import failed. This round may already be imported."}
|
const msg = ((importLatestMutation.error || importRoundMutation.error) as Error)?.message ?? "";
|
||||||
|
if (msg.includes("502") || msg.includes("Cloudflare") || msg.includes("origin") || msg.includes("Connection")) {
|
||||||
|
return (
|
||||||
|
<span className="text-red-400">
|
||||||
|
MITRE Evaluations API is temporarily unavailable (server overload or maintenance).
|
||||||
|
The import fetches live results from evals.mitre.org — please try again in a few minutes.
|
||||||
</span>
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (msg.includes("already imported") || msg.includes("409")) {
|
||||||
|
return <span className="text-yellow-400">This round has already been imported.</span>;
|
||||||
|
}
|
||||||
|
return <span className="text-red-400">{msg || "Import failed. This round may already be imported."}</span>;
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user