import { GROUP_NODE_LABEL, GROUP_PROPS, OUTCOME_NODE_LABEL, } from "@/lib/attack-paths/group-graph"; import { isProwlerFindingNode } from "@/lib/attack-paths/node-types"; /** * Color constants for attack path graph visualization * Colors chosen to work well in both light and dark themes */ /** * Node fill colors - darker versions of design system severity colors * Darkened to ensure white text has proper contrast (WCAG AA) */ export const GRAPH_NODE_COLORS = { // Finding severities - darkened versions for white text readability critical: "#cc0055", // Darker pink (from #ff006a) high: "#c45a3a", // Darker coral (from #f77852) medium: "#b8860b", // Dark goldenrod (from #fec94d) low: "#8b9a3e", // Olive/dark yellow-green (from #fdfbd4) info: "#2563eb", // Darker blue (from #3c8dff) // Node types prowlerFinding: "#ea580c", awsAccount: "#f59e0b", // Amber 500 - AWS orange attackPattern: "#16a34a", summary: "#16a34a", // Infrastructure ec2Instance: "#0891b2", // Cyan 600 s3Bucket: "#0284c7", // Sky 600 iamRole: "#7c3aed", // Violet 600 iamPolicy: "#7c3aed", lambdaFunction: "#d97706", // Amber 600 securityGroup: "#0891b2", default: "#0891b2", } as const; /** * Node border colors - using original design system colors as borders (lighter than fill) */ export const GRAPH_NODE_BORDER_COLORS = { critical: "#ff006a", // Original --bg-data-critical high: "#f77852", // Original --bg-data-high medium: "#fec94d", // Original --bg-data-medium low: "#c4d4a0", // Lighter olive info: "#3c8dff", // Original --bg-data-info prowlerFinding: "#fb923c", awsAccount: "#fbbf24", // Amber 400 attackPattern: "#4ade80", summary: "#4ade80", ec2Instance: "#22d3ee", // Cyan 400 s3Bucket: "#38bdf8", // Sky 400 iamRole: "#a78bfa", // Violet 400 iamPolicy: "#a78bfa", lambdaFunction: "#fbbf24", securityGroup: "#22d3ee", default: "#22d3ee", } as const; // Edge colors per theme export const GRAPH_EDGE_COLOR_DARK = "#ffffff"; // White for dark theme export const GRAPH_EDGE_COLOR_LIGHT = "#1e293b"; // Slate 800 for light theme export const GRAPH_EDGE_HIGHLIGHT_COLOR = "#34d399"; // Prowler green (hover/selection) export const GRAPH_EDGE_GLOW_COLOR = "#6ee7b7"; export const GRAPH_SELECTION_COLOR = "#ffffff"; export const GRAPH_BORDER_COLOR = "#374151"; export const GRAPH_ALERT_BORDER_COLOR = "#ef4444"; // Red 500 - for resources with findings // Terminal outcome node: distinct orange, independent of the resource palette. export const GRAPH_OUTCOME_FILL_COLOR = "#c2410c"; // Orange 700 export const GRAPH_OUTCOME_BORDER_COLOR = "#f97316"; // Orange 500 // Outline behind a group node's count badge, for contrast on any fill. export const GRAPH_COUNT_BADGE_STROKE_COLOR = "#0b1120"; // Slate 950 /** * Get node fill color based on labels and properties */ export const getNodeColor = ( labels: string[], properties?: Record, ): string => { if (labels.includes(OUTCOME_NODE_LABEL)) { return GRAPH_OUTCOME_FILL_COLOR; } if (labels.includes(GROUP_NODE_LABEL)) { const classLabel = String(properties?.[GROUP_PROPS.CLASS] ?? ""); return getNodeColor([classLabel]); } const isFinding = isProwlerFindingNode(labels); if (isFinding && properties?.severity) { const severity = String(properties.severity).toLowerCase(); if (severity === "critical") return GRAPH_NODE_COLORS.critical; if (severity === "high") return GRAPH_NODE_COLORS.high; if (severity === "medium") return GRAPH_NODE_COLORS.medium; if (severity === "low") return GRAPH_NODE_COLORS.low; if (severity === "informational" || severity === "info") return GRAPH_NODE_COLORS.info; return GRAPH_NODE_COLORS.prowlerFinding; } if (labels.some((l) => l.toLowerCase().includes("attackpattern"))) return GRAPH_NODE_COLORS.attackPattern; if (labels.includes("AWSAccount")) return GRAPH_NODE_COLORS.awsAccount; if (labels.includes("EC2Instance")) return GRAPH_NODE_COLORS.ec2Instance; if (labels.includes("S3Bucket")) return GRAPH_NODE_COLORS.s3Bucket; if (labels.includes("IAMRole")) return GRAPH_NODE_COLORS.iamRole; if (labels.includes("IAMPolicy")) return GRAPH_NODE_COLORS.iamPolicy; if (labels.includes("LambdaFunction")) return GRAPH_NODE_COLORS.lambdaFunction; if (labels.includes("SecurityGroup")) return GRAPH_NODE_COLORS.securityGroup; return GRAPH_NODE_COLORS.default; }; /** * Get node border color based on labels and properties */ export const getNodeBorderColor = ( labels: string[], properties?: Record, ): string => { if (labels.includes(OUTCOME_NODE_LABEL)) { return GRAPH_OUTCOME_BORDER_COLOR; } if (labels.includes(GROUP_NODE_LABEL)) { if (properties?.[GROUP_PROPS.HAS_FINDINGS]) { return GRAPH_ALERT_BORDER_COLOR; } const classLabel = String(properties?.[GROUP_PROPS.CLASS] ?? ""); return getNodeBorderColor([classLabel]); } const isFinding = isProwlerFindingNode(labels); if (isFinding && properties?.severity) { const severity = String(properties.severity).toLowerCase(); if (severity === "critical") return GRAPH_NODE_BORDER_COLORS.critical; if (severity === "high") return GRAPH_NODE_BORDER_COLORS.high; if (severity === "medium") return GRAPH_NODE_BORDER_COLORS.medium; if (severity === "low") return GRAPH_NODE_BORDER_COLORS.low; if (severity === "informational" || severity === "info") return GRAPH_NODE_BORDER_COLORS.info; return GRAPH_NODE_BORDER_COLORS.prowlerFinding; } if (labels.some((l) => l.toLowerCase().includes("attackpattern"))) return GRAPH_NODE_BORDER_COLORS.attackPattern; if (labels.includes("AWSAccount")) return GRAPH_NODE_BORDER_COLORS.awsAccount; if (labels.includes("EC2Instance")) return GRAPH_NODE_BORDER_COLORS.ec2Instance; if (labels.includes("S3Bucket")) return GRAPH_NODE_BORDER_COLORS.s3Bucket; if (labels.includes("IAMRole")) return GRAPH_NODE_BORDER_COLORS.iamRole; if (labels.includes("IAMPolicy")) return GRAPH_NODE_BORDER_COLORS.iamPolicy; if (labels.includes("LambdaFunction")) return GRAPH_NODE_BORDER_COLORS.lambdaFunction; if (labels.includes("SecurityGroup")) return GRAPH_NODE_BORDER_COLORS.securityGroup; return GRAPH_NODE_BORDER_COLORS.default; }; interface ResolveNodeColorsParams { labels: string[]; properties?: Record; selected?: boolean; hasFindings?: boolean; } interface NodeColorResult { fillColor: string; borderColor: string; } /** * Resolve fill and border colors for a graph node, layering selection and * finding-alert state on top of the label/severity defaults. */ export const resolveNodeColors = ({ labels, properties, selected, hasFindings, }: ResolveNodeColorsParams): NodeColorResult => { const fillColor = getNodeColor(labels, properties); const borderColor = selected ? GRAPH_EDGE_HIGHLIGHT_COLOR : hasFindings ? GRAPH_ALERT_BORDER_COLOR : getNodeBorderColor(labels, properties); return { fillColor, borderColor }; }; /** * Check if a background color is light (for determining text color) */ export const isLightBackground = (backgroundColor: string): boolean => { const hex = backgroundColor.replace("#", ""); const r = parseInt(hex.substring(0, 2), 16); const g = parseInt(hex.substring(2, 4), 16); const b = parseInt(hex.substring(4, 6), 16); const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; return luminance > 0.5; };