diff --git a/.github/workflows/attack-surface-reminder.yml b/.github/workflows/attack-surface-reminder.yml new file mode 100644 index 00000000..3882524d --- /dev/null +++ b/.github/workflows/attack-surface-reminder.yml @@ -0,0 +1,85 @@ +name: Attack Surface Data Reminder + +on: + pull_request_target: + types: [opened, synchronize] + branches: + - develop + +permissions: + contents: read + pull-requests: write + +jobs: + threat-vector-reminder: + runs-on: ubuntu-latest + steps: + - name: Check for threat vector changes + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.1.0 + with: + script: | + const { owner, repo } = context.repo; + const pull_number = context.payload.pull_request.number; + const MARKER = ''; + const THREAT_DATA_FILE = 'components/attack-surface/threatData.ts'; + + // Get files changed in this PR + const { data: prFiles } = await github.rest.pulls.listFiles({ + owner, + repo, + pull_number, + per_page: 100 + }); + + const threatDataChanged = prFiles.find(f => f.filename === THREAT_DATA_FILE); + + if (!threatDataChanged) { + console.log('threatData.ts not modified. Skipping.'); + return; + } + + // Check if we already posted a reminder for this PR + const { data: comments } = await github.rest.issues.listComments({ + owner, + repo, + issue_number: pull_number, + per_page: 100 + }); + + const existingReminder = comments.find(c => + c.body.includes(MARKER) && + c.user.login === 'github-actions[bot]' + ); + + if (existingReminder) { + console.log('Reminder already posted. Skipping.'); + return; + } + + const body = `### Attack Surface Configuration Reminder + ${MARKER} + + This PR modifies the attack surface threat data (\`${THREAT_DATA_FILE}\`). + + If you are adding a new threat vector, please ensure: + - [ ] The vector has all required fields: \`id\`, \`title\`, \`subtitle\`, \`category\`, \`severity\`, \`description\`, \`attackTags\`, \`primaryLink\`, \`primaryLinkLabel\`, and \`frameworkLinks\` + - [ ] The \`primaryLink\` points to a valid, existing framework page + - [ ] The \`attackTags\` array contains 3-4 short example attack types + - [ ] The \`category\` is one of: \`smart-contract\`, \`operational\`, \`human\`, \`infrastructure\`, \`supply-chain\`, \`governance\` + - [ ] The \`severity\` is one of: \`critical\`, \`high\`, \`medium\` + + If you are modifying an existing vector, verify that all links still resolve correctly. + + See the [Attack Surface Overview page](/attack-surface) on the preview deployment to verify the radial map renders correctly. + + --- + This is an automated reminder. If this PR doesn't affect threat vectors, you can ignore this message.`; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: pull_number, + body + }); + + console.log('Posted attack surface reminder comment.'); diff --git a/components/attack-surface/AttackSurface.css b/components/attack-surface/AttackSurface.css new file mode 100644 index 00000000..32637cf7 --- /dev/null +++ b/components/attack-surface/AttackSurface.css @@ -0,0 +1,405 @@ +/* Attack Surface — Radial Threat Map */ + +.as-wrap { + max-width: 860px; + margin: 0 auto; +} + +/* Summary Bar */ +.as-summary-bar { + display: flex; + align-items: center; + gap: 20px; + flex-wrap: wrap; + margin-bottom: 12px; + font-size: 14px; + color: var(--color-text-muted); +} + +.as-summary-count { + font-weight: 600; + color: var(--color-text-strong); + font-size: 15px; +} + +.as-summary-legend { + display: flex; + gap: 14px; + align-items: center; +} + +.as-legend-item { + display: flex; + align-items: center; + gap: 5px; + font-size: 13px; +} + +.as-legend-dot { + width: 10px; + height: 10px; + border-radius: 50%; + flex-shrink: 0; +} + +/* SVG Map Container — 10% bigger */ +.as-map { + width: 100%; + max-width: 770px; + margin: 0 auto; + display: block; +} + +.as-map svg { + width: 100%; + height: auto; +} + +/* Spokes */ +.as-spoke { + stroke-width: 2; + transition: stroke 0.3s ease; +} + +/* Hub */ +.as-hub { + fill: var(--color-primary); + transition: fill 0.3s ease; +} + +.as-hub-label { + fill: #ffffff; + font-size: 13px; + font-weight: 600; + pointer-events: none; + user-select: none; +} + +/* Nodes */ +.as-node-group { + cursor: pointer; + transition: transform 0.2s ease, filter 0.2s ease; + transform-box: fill-box; + transform-origin: center; +} + +.as-node { + cursor: pointer; + transition: fill 0.3s ease, stroke 0.3s ease; +} + +.as-node-group:hover .as-node { + filter: brightness(1.15); +} + +.as-node-group.selected { + transform: scale(1.1); +} + +.as-node-group.selected.state-no { + filter: drop-shadow(0 0 12px rgba(239, 68, 68, 0.6)); +} + +.as-node-group.selected.state-yes { + filter: drop-shadow(0 0 12px rgba(16, 185, 129, 0.6)); +} + +.as-node-group.selected.state-partial { + filter: drop-shadow(0 0 12px rgba(245, 158, 11, 0.6)); +} + +.as-node-check { + fill: #ffffff; + font-size: 18px; + font-weight: 700; + pointer-events: none; + user-select: none; +} + +.as-node-label { + fill: var(--color-text-primary); + font-size: 11px; + font-weight: 500; + user-select: none; + text-anchor: middle; + cursor: pointer; + transition: fill 0.15s ease; +} + +.as-node-group:hover .as-node-label { + fill: var(--color-primary); +} + +/* State colors */ +.as-node.state-no { fill: #ef4444; } +.as-node.state-yes { fill: #10b981; } +.as-node.state-partial { fill: #f59e0b; } + +.as-spoke.state-no { stroke: rgba(239, 68, 68, 0.3); } +.as-spoke.state-yes { stroke: rgba(16, 185, 129, 0.3); } +.as-spoke.state-partial { stroke: rgba(245, 158, 11, 0.3); } + +/* Critical pulse for unaddressed critical nodes */ +@keyframes as-pulse { + 0%, 100% { filter: drop-shadow(0 0 4px rgba(239, 68, 68, 0.4)); } + 50% { filter: drop-shadow(0 0 12px rgba(239, 68, 68, 0.7)); } +} + +.as-node-group.critical.state-no .as-node { + animation: as-pulse 2.5s ease-in-out infinite; +} + +/* ===== Detail Card ===== */ +.as-detail { + margin-top: 24px; + padding: 24px 28px; + background: var(--bg-card); + border: 1px solid var(--border-card); + border-radius: 8px; + animation: as-slide-in 0.2s ease-out; +} + +@keyframes as-slide-in { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + +.as-detail-top { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + margin-bottom: 14px; +} + +.as-detail-left { + flex: 1; + min-width: 0; +} + +.as-detail-title { + font-size: 22px; + font-weight: 700; + color: var(--color-text-strong); + margin: 0 0 4px; +} + +.as-detail-sev { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.06em; +} + +.as-detail-sev-dot { + width: 8px; + height: 8px; + border-radius: 50%; +} + +.as-detail-right { + display: flex; + align-items: center; + gap: 12px; + flex-shrink: 0; +} + +/* GAP / IN PROGRESS / SECURED toggle */ +.as-detail-toggle { + display: flex; + border: 1px solid var(--border-card); + border-radius: 4px; + overflow: hidden; +} + +.as-toggle-btn { + padding: 7px 16px; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + cursor: pointer; + border: none; + background: transparent; + color: var(--color-text-muted); + transition: all 0.15s ease; +} + +.as-toggle-btn:not(:last-child) { + border-right: 1px solid var(--border-card); +} + +.as-toggle-btn:hover { + background: rgba(255, 255, 255, 0.05); +} + +:root:not(.dark) .as-toggle-btn:hover { + background: rgba(0, 0, 0, 0.03); +} + +.as-toggle-btn.active.state-no { + background: #ef4444; + color: #ffffff; +} + +.as-toggle-btn.active.state-partial { + background: #f59e0b; + color: #ffffff; +} + +.as-toggle-btn.active.state-yes { + background: #10b981; + color: #ffffff; +} + +.as-detail-close { + background: none; + border: none; + color: var(--color-text-muted); + cursor: pointer; + font-size: 22px; + padding: 0 4px; + line-height: 1; + transition: color 0.15s ease; +} + +.as-detail-close:hover { + color: var(--color-text-strong); +} + +/* Description */ +.as-detail-desc { + font-size: 15px; + line-height: 1.6; + color: var(--color-text-muted); + margin: 0 0 16px; +} + +/* Bottom row: tags + CTA link */ +.as-detail-bottom { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 16px; + flex-wrap: wrap; +} + +.as-detail-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.as-attack-tag { + font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; + font-size: 12px; + padding: 4px 12px; + border: 1px solid var(--border-card); + border-radius: 4px; + color: var(--color-text-muted); + white-space: nowrap; +} + +.as-detail-cta { + font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; + font-size: 13px; + padding: 8px 20px; + border: 1px solid var(--color-primary); + border-radius: 4px; + color: var(--color-primary); + text-decoration: none; + white-space: nowrap; + transition: all 0.15s ease; + flex-shrink: 0; +} + +.as-detail-cta:hover { + background: rgba(67, 57, 219, 0.08); +} + +/* ===== Mobile list ===== */ +.as-mobile-list { + display: none; + flex-direction: column; + gap: 8px; +} + +.as-mobile-row { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 14px; + background: var(--bg-card); + border: 1px solid var(--border-card); + border-radius: 8px; + cursor: pointer; + transition: background 0.15s ease; +} + +.as-mobile-row:hover { + background: rgba(67, 57, 219, 0.04); +} + +.as-mobile-row.selected { + border-color: var(--color-text-strong); +} + +.as-mobile-dot { + width: 20px; + height: 20px; + border-radius: 50%; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 700; + color: #ffffff; + cursor: pointer; + transition: background 0.2s ease; +} + +.as-mobile-dot.state-no { background: #ef4444; } +.as-mobile-dot.state-yes { background: #10b981; } +.as-mobile-dot.state-partial { background: #f59e0b; } + +.as-mobile-name { + font-size: 14px; + font-weight: 500; + color: var(--color-text-strong); + flex: 1; +} + +.as-mobile-sev { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + padding: 2px 8px; + border-radius: 4px; + flex-shrink: 0; +} + +@media (max-width: 600px) { + .as-map { display: none; } + .as-mobile-list { display: flex; } + + .as-detail-top { + flex-direction: column; + } + + .as-detail-bottom { + flex-direction: column; + align-items: flex-start; + } +} + +/* Print */ +@media print { + .as-detail-close, + .as-detail-toggle { display: none; } + .as-mobile-dot { print-color-adjust: exact; -webkit-print-color-adjust: exact; } + .as-node { print-color-adjust: exact; -webkit-print-color-adjust: exact; } +} diff --git a/components/attack-surface/AttackSurfaceDashboard.tsx b/components/attack-surface/AttackSurfaceDashboard.tsx new file mode 100644 index 00000000..bc62050d --- /dev/null +++ b/components/attack-surface/AttackSurfaceDashboard.tsx @@ -0,0 +1,332 @@ +import { useState, useCallback, useEffect, useMemo } from "react"; +import { threatVectors, severityMeta, type PostureState, type ThreatVector } from "./threatData"; +import "./AttackSurface.css"; + +const STORAGE_KEY = "attackSurface-posture"; +const CX = 420; +const CY = 420; +const RADIUS = 286; +const NODE_R = 39; +const HUB_R = 39; + +type PostureMap = Record; + +const nextState: Record = { + no: "yes", + yes: "partial", + partial: "no", +}; + +const stateColors: Record = { + no: "#ef4444", + yes: "#10b981", + partial: "#f59e0b", +}; + +const spokeColors: Record = { + no: "rgba(239, 68, 68, 0.25)", + yes: "rgba(16, 185, 129, 0.25)", + partial: "rgba(245, 158, 11, 0.25)", +}; + +const checkMarks: Record = { + no: "", + yes: "\u2713", + partial: "", +}; + +const postureLabels: Record = { + no: "Gap", + yes: "Secured", + partial: "In Progress", +}; + +function loadPosture(): PostureMap { + try { + const raw = localStorage.getItem(STORAGE_KEY); + return raw ? JSON.parse(raw) : {}; + } catch { + return {}; + } +} + +function savePosture(posture: PostureMap) { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(posture)); + } catch {} +} + +function wrapLabel(title: string): string[] { + const words = title.split(" "); + const lines: string[] = []; + let current = ""; + for (const word of words) { + if (current && (current + " " + word).length > 16) { + lines.push(current); + current = word; + } else { + current = current ? current + " " + word : word; + } + } + if (current) lines.push(current); + return lines; +} + +function nodePosition(index: number, total: number) { + const angle = ((2 * Math.PI) / total) * index - Math.PI / 2; + return { + x: CX + RADIUS * Math.cos(angle), + y: CY + RADIUS * Math.sin(angle), + }; +} + +export function AttackSurfaceDashboard() { + const [posture, setPosture] = useState({}); + const [selected, setSelected] = useState(null); + + useEffect(() => { + setPosture(loadPosture()); + }, []); + + const handleToggle = useCallback((id: string) => { + setPosture((prev) => { + const current = prev[id] || "no"; + const next = { ...prev, [id]: nextState[current] }; + if (nextState[current] === "no") delete next[id]; + savePosture(next); + return next; + }); + }, []); + + const handleSetPosture = useCallback((id: string, state: PostureState) => { + setPosture((prev) => { + const next = { ...prev, [id]: state }; + if (state === "no") delete next[id]; + savePosture(next); + return next; + }); + }, []); + + const handleSelect = useCallback((id: string) => { + setSelected((prev) => (prev === id ? null : id)); + }, []); + + const counts = useMemo(() => { + const values = threatVectors.map((v) => posture[v.id] || "no"); + return { + yes: values.filter((s) => s === "yes").length, + partial: values.filter((s) => s === "partial").length, + no: values.filter((s) => s === "no").length, + }; + }, [posture]); + + const selectedVector = selected + ? threatVectors.find((v) => v.id === selected) || null + : null; + + return ( +
+ {/* Summary */} +
+ + {counts.yes + counts.partial} of {threatVectors.length} vectors covered + +
+ + + {counts.yes} secured + + + + {counts.partial} in progress + + + + {counts.no} gaps + +
+
+ + {/* SVG Radial Map */} +
+ + {/* Spokes */} + {threatVectors.map((v, i) => { + const pos = nodePosition(i, threatVectors.length); + const state = posture[v.id] || "no"; + return ( + + ); + })} + + {/* Hub */} + + + Your + + + Protocol + + + {/* Nodes */} + {threatVectors.map((v, i) => { + const pos = nodePosition(i, threatVectors.length); + const state = posture[v.id] || "no"; + const isCritical = v.severity === "critical"; + const isSelected = selected === v.id; + const lines = wrapLabel(v.title); + + return ( + handleSelect(v.id)} + style={{ cursor: "pointer" }} + > + {/* Main node */} + + {/* Check mark */} + + {checkMarks[state]} + + {/* Label */} + {lines.map((line, li) => ( + + {line} + + ))} + + ); + })} + +
+ + {/* Mobile list fallback */} +
+ {threatVectors.map((v) => { + const state = posture[v.id] || "no"; + const sev = severityMeta[v.severity]; + return ( +
handleSelect(v.id)} + > +
{ + e.stopPropagation(); + handleToggle(v.id); + }} + > + {checkMarks[state]} +
+ {v.title} + + {sev.label} + +
+ ); + })} +
+ + {/* Detail Card */} + {selectedVector && ( + handleSetPosture(selectedVector.id, state)} + onClose={() => setSelected(null)} + /> + )} +
+ ); +} + +function DetailCard({ + vector, + state, + onSetState, + onClose, +}: { + vector: ThreatVector; + state: PostureState; + onSetState: (state: PostureState) => void; + onClose: () => void; +}) { + const sev = severityMeta[vector.severity]; + + return ( +
+
+
+

{vector.title}

+
+ + {sev.label.toUpperCase()} SEVERITY +
+
+
+
+ {(["no", "partial", "yes"] as PostureState[]).map((s) => ( + + ))} +
+ +
+
+ +

{vector.description}

+ +
+
+ {vector.attackTags.map((tag) => ( + {tag} + ))} +
+ + {vector.primaryLinkLabel} → + +
+
+ ); +} diff --git a/components/attack-surface/threatData.ts b/components/attack-surface/threatData.ts new file mode 100644 index 00000000..16af821a --- /dev/null +++ b/components/attack-surface/threatData.ts @@ -0,0 +1,259 @@ +export type Severity = "critical" | "high" | "medium"; +export type PostureState = "no" | "yes" | "partial"; +export type Category = "smart-contract" | "operational" | "human" | "infrastructure" | "supply-chain" | "governance"; + +export interface FrameworkLink { + label: string; + href: string; +} + +export interface ThreatVector { + id: string; + title: string; + subtitle: string; + category: Category; + severity: Severity; + description: string; + example?: string; + /** Short attack-type tags shown in the detail card */ + attackTags: string[]; + /** The primary framework page for this vector — label text links here */ + primaryLink: string; + /** Label for the primary link button in the detail card */ + primaryLinkLabel: string; + frameworkLinks: FrameworkLink[]; +} + +export const categoryMeta: Record = { + "smart-contract": { label: "Smart Contract", color: "#ef4444" }, + operational: { label: "Operational", color: "#f97316" }, + human: { label: "Human", color: "#eab308" }, + infrastructure: { label: "Infrastructure", color: "#8b5cf6" }, + "supply-chain": { label: "Supply Chain", color: "#3b82f6" }, + governance: { label: "Governance", color: "#10b981" }, +}; + +export const severityMeta: Record = { + critical: { label: "Critical", color: "#ef4444", bg: "rgba(239, 68, 68, 0.12)" }, + high: { label: "High", color: "#f97316", bg: "rgba(249, 115, 22, 0.12)" }, + medium: { label: "Medium", color: "#eab308", bg: "rgba(234, 179, 8, 0.12)" }, +}; + +export const threatVectors: ThreatVector[] = [ + { + id: "smart-contract-exploits", + title: "Smart Contract Exploits", + subtitle: "Code vulnerabilities in on-chain logic", + category: "smart-contract", + severity: "critical", + description: + "Reentrancy attacks, logic bugs, oracle manipulation, flash loan exploits, and unaudited upgrade paths that drain protocol funds.", + example: "Euler Finance: $197M flash loan exploit", + attackTags: ["Reentrancy", "Flash loan exploit", "Oracle manipulation", "Logic bug"], + primaryLink: "/external-security-reviews/overview", + primaryLinkLabel: "Security Reviews Framework", + frameworkLinks: [ + { label: "Incident Playbooks", href: "/incident-management/playbooks" }, + { label: "DevSecOps", href: "/devsecops/overview" }, + { label: "External Security Reviews", href: "/external-security-reviews/overview" }, + { label: "Security Testing", href: "/security-testing/overview" }, + ], + }, + { + id: "multisig-failures", + title: "Multisig Operational Failures", + subtitle: "Signer and threshold mismanagement", + category: "operational", + severity: "critical", + description: + "Signer unavailability, lost keys, weak thresholds, no rotation policy, and lack of emergency procedures leave protocol treasuries exposed.", + example: "Ronin Bridge: $625M via compromised validator keys", + attackTags: ["Signer unavailability", "Weak threshold", "Key loss", "No rotation policy"], + primaryLink: "/multisig-for-protocols/overview", + primaryLinkLabel: "Multisig Framework", + frameworkLinks: [ + { label: "Multisig Overview", href: "/multisig-for-protocols/overview" }, + { label: "Emergency Procedures", href: "/multisig-for-protocols/emergency-procedures" }, + { label: "Signer Onboarding", href: "/multisig-for-protocols/signer-onboarding" }, + { label: "Multisig Runbooks", href: "/multisig-for-protocols/runbooks/overview" }, + ], + }, + { + id: "dprk-threat-actors", + title: "DPRK / Threat Actor Hiring", + subtitle: "Nation-state infiltration via hiring", + category: "human", + severity: "critical", + description: + "Unknowingly hiring North Korean IT workers or other threat actors as contractors who gain access to codebases, infrastructure, and signing keys.", + example: "Multiple protocols compromised via DPRK contractors", + attackTags: ["Fake identity", "Contractor infiltration", "Code backdoor", "Key theft"], + primaryLink: "/incident-management/playbooks/dprk-it-worker", + primaryLinkLabel: "DPRK Playbook", + frameworkLinks: [ + { label: "DPRK IT Worker Playbook", href: "/incident-management/playbooks/dprk-it-worker" }, + { label: "Insider Threat Mitigation", href: "/opsec/control-domains/people/insider-threat-mitigation" }, + { label: "Personnel Controls", href: "/opsec/control-domains/people/overview" }, + ], + }, + { + id: "leadership-phishing", + title: "Leadership Phishing", + subtitle: "Targeted spearphishing of key personnel", + category: "human", + severity: "critical", + description: + "Spearphishing campaigns targeting founders, executives, and multisig signers to steal credentials, signing keys, or trick them or employees into approving malicious transactions.", + attackTags: ["Spearphishing", "Credential theft", "Malicious signing", "Impersonation"], + primaryLink: "/awareness/understanding-threat-vectors", + primaryLinkLabel: "Threat Vectors Guide", + frameworkLinks: [ + { label: "Understanding Threat Vectors", href: "/awareness/understanding-threat-vectors" }, + { label: "Phishing & Social Engineering", href: "/user-team-security/phishing-social-engineering" }, + { label: "Security Training", href: "/user-team-security/security-training" }, + ], + }, + { + id: "infrastructure-compromise", + title: "Infrastructure Compromise", + subtitle: "Cloud, server, and network breaches", + category: "infrastructure", + severity: "critical", + description: + "Cloud misconfigurations, exposed APIs, server compromise, weak network segmentation, and missing zero-trust architecture leading to full infrastructure takeover.", + attackTags: ["AWS key leak", "RPC manipulation", "Server compromise", "API exfiltration"], + primaryLink: "/infrastructure/overview", + primaryLinkLabel: "Infrastructure Framework", + frameworkLinks: [ + { label: "Infrastructure Overview", href: "/infrastructure/overview" }, + { label: "Cloud Security", href: "/infrastructure/cloud-security" }, + { label: "Network Security", href: "/infrastructure/network-security" }, + { label: "Zero Trust Architecture", href: "/infrastructure/zero-trust-architecture" }, + ], + }, + { + id: "frontend-dns-hijacking", + title: "Frontend / DNS Hijacking", + subtitle: "Website and domain takeover attacks", + category: "infrastructure", + severity: "critical", + description: + "DNS hijacking, compromised frontend deployments, UI spoofing, and registrar account takeovers that redirect users to malicious interfaces draining wallets.", + example: "Curve Finance: DNS hijack redirected users to drainer", + attackTags: ["DNS hijack", "UI spoofing", "Registrar takeover", "Frontend injection"], + primaryLink: "/infrastructure/domain-and-dns-security/overview", + primaryLinkLabel: "DNS Security Framework", + frameworkLinks: [ + { label: "DNS Security", href: "/infrastructure/domain-and-dns-security/overview" }, + { label: "Monitoring & Alerting", href: "/infrastructure/domain-and-dns-security/monitoring-and-alerting" }, + { label: "Front-End Security", href: "/front-end-web-application/overview" }, + { label: "DNS Registrar Cert", href: "/certs/sfc-dns-registrar" }, + ], + }, + { + id: "opsec-failures", + title: "Operational Security Failures", + subtitle: "Day-to-day security hygiene gaps", + category: "operational", + severity: "high", + description: + "Poor device hygiene, leaked credentials, weak access controls, unencrypted communications, and lack of security policies across the team.", + attackTags: ["Leaked credentials", "Weak access controls", "Unencrypted comms", "No MFA"], + primaryLink: "/opsec/overview", + primaryLinkLabel: "OpSec Framework", + frameworkLinks: [ + { label: "OpSec Overview", href: "/opsec/overview" }, + { label: "Technical Controls", href: "/opsec/control-domains/technical/overview" }, + { label: "Device Hardening", href: "/opsec/control-domains/technical/device-hardening" }, + { label: "IAM", href: "/iam/overview" }, + ], + }, + { + id: "supply-chain-attacks", + title: "Supply Chain Attacks", + subtitle: "Compromised dependencies and CI/CD", + category: "supply-chain", + severity: "high", + description: + "Dependency poisoning, typosquatting, compromised CI/CD pipelines, malicious npm/crate packages, and build system backdoors that inject malicious code.", + example: "Ledger Connect Kit: compromised npm package", + attackTags: ["Dependency poisoning", "Typosquatting", "CI/CD backdoors", "Malicious package"], + primaryLink: "/supply-chain/overview", + primaryLinkLabel: "Supply Chain Framework", + frameworkLinks: [ + { label: "Supply Chain Overview", href: "/supply-chain/overview" }, + { label: "DevSecOps", href: "/devsecops/overview" }, + { label: "CI/CD Security", href: "/devsecops/ci-cd-security" }, + { label: "Code Signing", href: "/devsecops/code-signing" }, + ], + }, + { + id: "monitoring-gaps", + title: "Monitoring & Alerting Gaps", + subtitle: "Blind spots in threat detection", + category: "operational", + severity: "high", + description: + "No on-chain monitoring, slow anomaly detection, missing alerts for critical operations, and no automated response — letting attackers operate undetected.", + attackTags: ["No on-chain monitoring", "Slow detection", "Missing alerts", "No auto-response"], + primaryLink: "/monitoring/overview", + primaryLinkLabel: "Monitoring Framework", + frameworkLinks: [ + { label: "Monitoring Overview", href: "/monitoring/overview" }, + { label: "Threat Detection", href: "/security-automation/threat-detection-response" }, + { label: "DNS Monitoring", href: "/infrastructure/domain-and-dns-security/monitoring-and-alerting" }, + ], + }, + { + id: "social-engineering", + title: "Social Engineering", + subtitle: "Manipulation beyond phishing", + category: "human", + severity: "high", + description: + "Impersonation of partners or investors, fake collaboration requests, community manipulation, and trust exploitation to gain access or influence decisions.", + attackTags: ["Impersonation", "Fake partnership", "Community manipulation", "Trust exploit"], + primaryLink: "/awareness/overview", + primaryLinkLabel: "Awareness Framework", + frameworkLinks: [ + { label: "Awareness Overview", href: "/awareness/overview" }, + { label: "Threat Vectors", href: "/awareness/understanding-threat-vectors" }, + { label: "Security Culture", href: "/user-team-security/security-aware-culture" }, + { label: "Community Management", href: "/community-management/overview" }, + ], + }, + { + id: "duress-situations", + title: "Duress Situations", + subtitle: "Physical threats and coercion", + category: "human", + severity: "high", + description: + "Physical threats, kidnapping, extortion, and coercion targeting key personnel to force transaction signing or credential disclosure.", + attackTags: ["Physical threat", "Kidnapping", "Extortion", "Forced signing"], + primaryLink: "/opsec/travel/guide", + primaryLinkLabel: "Travel Security Guide", + frameworkLinks: [ + { label: "Travel Security", href: "/opsec/travel/guide" }, + { label: "Emergency Procedures", href: "/multisig-for-protocols/emergency-procedures" }, + { label: "Physical Controls", href: "/opsec/control-domains/physical-environmental/overview" }, + ], + }, + { + id: "governance-attacks", + title: "Governance Attacks", + subtitle: "Malicious proposals and vote manipulation", + category: "governance", + severity: "medium", + description: + "Proposal manipulation, vote buying, flash loan governance attacks, and unauthorized upgrades that alter protocol behavior or drain treasuries.", + attackTags: ["Proposal manipulation", "Vote buying", "Rogue upgrades"], + primaryLink: "/governance/overview", + primaryLinkLabel: "Governance Framework", + frameworkLinks: [ + { label: "Governance Overview", href: "/governance/overview" }, + { label: "Security Council Best Practices", href: "/governance/security-council-best-practices" }, + { label: "Multisig for Protocols", href: "/multisig-for-protocols/overview" }, + ], + }, +]; diff --git a/components/index.ts b/components/index.ts index 8ee66ac6..c5a48ed3 100644 --- a/components/index.ts +++ b/components/index.ts @@ -24,3 +24,4 @@ export { CertifiedProtocols } from './certified-protocols/CertifiedProtocols' export { CertifiedProtocolsWrapper } from './certified-protocols/CertifiedProtocolsWrapper' export { BadgeLegend } from './contributors/BadgeLegend' export { DevOnly } from './dev-only/DevOnly' +export { AttackSurfaceDashboard } from './attack-surface/AttackSurfaceDashboard' diff --git a/docs/pages/attack-surface.mdx b/docs/pages/attack-surface.mdx new file mode 100644 index 00000000..04de333b --- /dev/null +++ b/docs/pages/attack-surface.mdx @@ -0,0 +1,15 @@ +--- +title: "Attack Surface Overview | SEAL Security Frameworks" +description: "Visual overview of the Web3 threat landscape. See how protocols get compromised, assess your security posture, and find remediation guides." +tags: + - Security Specialist + - Operations & Strategy +--- + +import { AttackSurfaceDashboard } from '../../components' + +# Attack Surface Overview + +See where your protocol is most exposed. Click the checkbox on each vector to track your security posture. Your progress is saved locally in your browser. + + diff --git a/vocs.config.tsx b/vocs.config.tsx index 8f50b117..7159eb20 100644 --- a/vocs.config.tsx +++ b/vocs.config.tsx @@ -37,6 +37,7 @@ const config = { text: 'Introduction', collapsed: false, items: [ + { text: 'Attack Surface Overview', link: '/attack-surface', dev: true }, { text: 'Introduction to Frameworks', link: '/intro/introduction' }, { text: 'How to Navigate the Website', link: '/intro/how-to-navigate-the-website' }, { text: 'Overview of each Framework', link: '/intro/overview-of-each-framework' },