diff --git a/web/public/app.js b/web/public/app.js
index 252a8bf..38a7cab 100644
--- a/web/public/app.js
+++ b/web/public/app.js
@@ -595,11 +595,9 @@ $('#findingModal').addEventListener('click', (e) => { if (e.target.id === 'findi
const KILL_CHAIN_STAGES = ['recon', 'initial-access', 'execution', 'privesc', 'lateral', 'exfil', 'impact'];
-// Bright, saturated palette for the dark graph canvas — the severity chip
-// colors elsewhere are tuned for text-on-light-background legibility and
-// read as muddy on a dark node graph.
-const CANVAS_SEV_COLOR = { critical: '#ff6b5b', high: '#ffab52', medium: '#f0cf5c', low: '#7fd99a', info: '#8fa3ef' };
-function canvasColor(sev) { return CANVAS_SEV_COLOR[['critical', 'high', 'medium', 'low', 'info'][sevRank(sev)]]; }
+// Same severity tokens the rest of the console uses — the graph canvas
+// follows the light/dark theme instead of a fixed dark palette.
+function canvasColor(sev) { return `var(--sev-${['critical', 'high', 'medium', 'low', 'info'][sevRank(sev)]}-fg)`; }
function nodeIcon(f) {
const t = `${f.title} ${f.evidence} ${f.cwe} ${f.stage}`.toLowerCase();
@@ -671,20 +669,20 @@ function renderAttackPath(container, findings, target) {
const nodeSvg = (n) => {
if (n.root) {
return `
-
- 🎯
- ${esc(trimMid(n.label, 26))}
+
+ 🎯
+ ${esc(trimMid(n.label, 26))}
`;
}
const f = n.finding;
const color = canvasColor(f.severity);
const x = n.x - NODE_W / 2, y = n.y - NODE_H / 2;
return `
-
+
${nodeIcon(f)}
- ${esc(trimMid(f.title, 22))}
- ${esc((f.mitre || f.owasp || f.cwe || n.stageLabel || '').slice(0, 26))}
-
+ ${esc(trimMid(f.title, 22))}
+ ${esc((f.mitre || f.owasp || f.cwe || n.stageLabel || '').slice(0, 26))}
+
`;
};
@@ -692,8 +690,8 @@ function renderAttackPath(container, findings, target) {
${!hasStages ? '
No kill-chain stage data yet — shown as a flat graph from the target.
' : ''}
diff --git a/web/public/style.css b/web/public/style.css
index 1122838..639e088 100644
--- a/web/public/style.css
+++ b/web/public/style.css
@@ -311,14 +311,10 @@ textarea { resize: vertical; min-height: 72px; }
/* Generative Attack Path Chaining */
.attackpath-empty { font-size: 12.5px; color: var(--text-faint); padding: var(--sp-5); text-align: center; border: 1px dashed var(--border-strong); border-radius: var(--radius-sm); }
-/* The graph canvas is intentionally fixed-dark regardless of the app theme —
- a node/edge map reads better with bright severity colors against a near-
- black surface, the way NodeZero/attack-graph tools render it, and it stays
- legible whether the rest of the console is in light or dark mode. */
-.ap-canvas-wrap { border-radius: var(--radius-md); overflow: auto; background: #0f1115; border: 1px solid #24262d; }
+.ap-canvas-wrap { border-radius: var(--radius-md); overflow: auto; background: var(--surface-2); border: 1px solid var(--border); }
.ap-canvas { display: block; min-width: 100%; }
.ap-canvas text { font-family: var(--sans); }
-.ap-node-g:hover rect:first-child { filter: brightness(1.35); }
+.ap-node-g:hover rect:first-child { filter: brightness(0.97); }
/* findings table */
.data-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }