opena2a-cli 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +225 -1
- package/dist/commands/guard-hooks.d.ts +27 -0
- package/dist/commands/guard-hooks.d.ts.map +1 -0
- package/dist/commands/guard-hooks.js +207 -0
- package/dist/commands/guard-hooks.js.map +1 -0
- package/dist/commands/guard-policy.d.ts +54 -0
- package/dist/commands/guard-policy.d.ts.map +1 -0
- package/dist/commands/guard-policy.js +251 -0
- package/dist/commands/guard-policy.js.map +1 -0
- package/dist/commands/guard-signing.d.ts +52 -0
- package/dist/commands/guard-signing.d.ts.map +1 -0
- package/dist/commands/guard-signing.js +185 -0
- package/dist/commands/guard-signing.js.map +1 -0
- package/dist/commands/guard-snapshots.d.ts +54 -0
- package/dist/commands/guard-snapshots.d.ts.map +1 -0
- package/dist/commands/guard-snapshots.js +346 -0
- package/dist/commands/guard-snapshots.js.map +1 -0
- package/dist/commands/guard.d.ts +60 -4
- package/dist/commands/guard.d.ts.map +1 -1
- package/dist/commands/guard.js +475 -95
- package/dist/commands/guard.js.map +1 -1
- package/dist/commands/init.js +3 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/review.d.ts +110 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +634 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/shield.d.ts +3 -0
- package/dist/commands/shield.d.ts.map +1 -1
- package/dist/commands/shield.js +488 -34
- package/dist/commands/shield.js.map +1 -1
- package/dist/index.js +36 -6
- package/dist/index.js.map +1 -1
- package/dist/report/review-html.d.ts +16 -0
- package/dist/report/review-html.d.ts.map +1 -0
- package/dist/report/review-html.js +579 -0
- package/dist/report/review-html.js.map +1 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +2 -1
- package/dist/router.js.map +1 -1
- package/dist/shield/ai-tool-config.d.ts +49 -0
- package/dist/shield/ai-tool-config.d.ts.map +1 -0
- package/dist/shield/ai-tool-config.js +169 -0
- package/dist/shield/ai-tool-config.js.map +1 -0
- package/dist/shield/arp-bridge.d.ts +62 -0
- package/dist/shield/arp-bridge.d.ts.map +1 -0
- package/dist/shield/arp-bridge.js +198 -0
- package/dist/shield/arp-bridge.js.map +1 -0
- package/dist/shield/baselines.d.ts +58 -0
- package/dist/shield/baselines.d.ts.map +1 -0
- package/dist/shield/baselines.js +371 -0
- package/dist/shield/baselines.js.map +1 -0
- package/dist/shield/findings.d.ts +52 -0
- package/dist/shield/findings.d.ts.map +1 -0
- package/dist/shield/findings.js +336 -0
- package/dist/shield/findings.js.map +1 -0
- package/dist/shield/init.d.ts +3 -0
- package/dist/shield/init.d.ts.map +1 -1
- package/dist/shield/init.js +145 -12
- package/dist/shield/init.js.map +1 -1
- package/dist/shield/integrity.d.ts.map +1 -1
- package/dist/shield/integrity.js +6 -2
- package/dist/shield/integrity.js.map +1 -1
- package/dist/shield/report-html.d.ts +29 -0
- package/dist/shield/report-html.d.ts.map +1 -0
- package/dist/shield/report-html.js +689 -0
- package/dist/shield/report-html.js.map +1 -0
- package/dist/shield/sarif.d.ts +65 -0
- package/dist/shield/sarif.d.ts.map +1 -0
- package/dist/shield/sarif.js +108 -0
- package/dist/shield/sarif.js.map +1 -0
- package/dist/shield/status.d.ts.map +1 -1
- package/dist/shield/status.js +19 -6
- package/dist/shield/status.js.map +1 -1
- package/dist/shield/types.d.ts +19 -1
- package/dist/shield/types.d.ts.map +1 -1
- package/dist/shield/types.js +2 -1
- package/dist/shield/types.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shield HTML Posture Report Generator.
|
|
4
|
+
*
|
|
5
|
+
* Generates a self-contained HTML file with:
|
|
6
|
+
* - Dark theme (slate-900 background, slate-800 cards)
|
|
7
|
+
* - Posture score circular gauge with grade letter
|
|
8
|
+
* - Severity breakdown horizontal bar chart
|
|
9
|
+
* - Agent activity table
|
|
10
|
+
* - Policy violations table with severity filter
|
|
11
|
+
* - Runtime protection, credential exposure, supply chain cards
|
|
12
|
+
* - Event timeline / narrative section
|
|
13
|
+
*
|
|
14
|
+
* Design tokens:
|
|
15
|
+
* Background: #0f172a (slate-900), Card: #1e293b (slate-800)
|
|
16
|
+
* Primary: #06b6d4 (teal), Score: teal
|
|
17
|
+
* Critical: #ef4444, High: #f97316, Medium: #eab308, Low: #3b82f6, Info: #6b7280
|
|
18
|
+
* Font: system monospace (JetBrains Mono fallback)
|
|
19
|
+
*
|
|
20
|
+
* No external dependencies. No emojis.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.generateExecutiveSummary = generateExecutiveSummary;
|
|
24
|
+
exports.generateShieldHtmlReport = generateShieldHtmlReport;
|
|
25
|
+
/**
|
|
26
|
+
* Generate the executive summary text from report data.
|
|
27
|
+
* No LLM needed -- deterministic sentence generation.
|
|
28
|
+
*/
|
|
29
|
+
function generateExecutiveSummary(report, findings, trend) {
|
|
30
|
+
const sentences = [];
|
|
31
|
+
// Sentence 1: Score + grade + trend
|
|
32
|
+
if (trend) {
|
|
33
|
+
const dir = trend.direction === 'improving' ? 'improved'
|
|
34
|
+
: trend.direction === 'declining' ? 'declined' : 'remained stable';
|
|
35
|
+
sentences.push(`Security posture score ${dir} from ${trend.previousScore}/${trend.previousGrade} to ${report.posture.score}/${report.posture.grade} over ${trend.periodDays} day${trend.periodDays !== 1 ? 's' : ''} (delta: ${trend.delta > 0 ? '+' : ''}${trend.delta}).`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
sentences.push(`Security posture score: ${report.posture.score}/100 (Grade ${report.posture.grade}). No previous snapshot available for trend comparison.`);
|
|
39
|
+
}
|
|
40
|
+
// Sentence 2: Top finding category + count
|
|
41
|
+
const criticalFindings = findings.filter(f => f.finding.severity === 'critical');
|
|
42
|
+
const highFindings = findings.filter(f => f.finding.severity === 'high');
|
|
43
|
+
if (criticalFindings.length > 0) {
|
|
44
|
+
const totalCrit = criticalFindings.reduce((sum, f) => sum + f.count, 0);
|
|
45
|
+
sentences.push(`${totalCrit} critical finding${totalCrit !== 1 ? 's' : ''} across ${criticalFindings.length} categor${criticalFindings.length !== 1 ? 'ies' : 'y'} require immediate attention.`);
|
|
46
|
+
}
|
|
47
|
+
else if (highFindings.length > 0) {
|
|
48
|
+
const totalHigh = highFindings.reduce((sum, f) => sum + f.count, 0);
|
|
49
|
+
sentences.push(`${totalHigh} high-severity finding${totalHigh !== 1 ? 's' : ''} detected. No critical findings.`);
|
|
50
|
+
}
|
|
51
|
+
else if (findings.length > 0) {
|
|
52
|
+
sentences.push(`${findings.length} finding${findings.length !== 1 ? 's' : ''} detected, none above medium severity.`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
sentences.push('No security findings detected in this reporting period.');
|
|
56
|
+
}
|
|
57
|
+
// Sentence 3: Policy posture
|
|
58
|
+
const pe = report.policyEvaluation;
|
|
59
|
+
if (pe.blocked > 0) {
|
|
60
|
+
sentences.push(`Policy enforcement is active: ${pe.blocked} action${pe.blocked !== 1 ? 's' : ''} blocked, ${pe.monitored} monitored.`);
|
|
61
|
+
}
|
|
62
|
+
else if (pe.monitored > 0) {
|
|
63
|
+
sentences.push(`Policy is in monitor-only mode: ${pe.monitored} action${pe.monitored !== 1 ? 's' : ''} logged but not blocked.`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
sentences.push('No policy enforcement activity recorded.');
|
|
67
|
+
}
|
|
68
|
+
// Sentence 4: Config integrity
|
|
69
|
+
const ci = report.configIntegrity;
|
|
70
|
+
if (ci.filesMonitored > 0) {
|
|
71
|
+
if (ci.tamperedFiles.length > 0) {
|
|
72
|
+
sentences.push(`WARNING: ${ci.tamperedFiles.length} of ${ci.filesMonitored} monitored config file${ci.filesMonitored !== 1 ? 's' : ''} show tampering.`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
sentences.push(`All ${ci.filesMonitored} monitored config file${ci.filesMonitored !== 1 ? 's' : ''} have valid signatures.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return sentences.join(' ');
|
|
79
|
+
}
|
|
80
|
+
function generateShieldHtmlReport(report, narrative, findings, trend) {
|
|
81
|
+
const findingsData = findings ?? [];
|
|
82
|
+
const trendData = trend ?? report.posture.trend ?? null;
|
|
83
|
+
const executiveSummary = generateExecutiveSummary(report, findingsData, trendData);
|
|
84
|
+
const jsonData = JSON.stringify({
|
|
85
|
+
report,
|
|
86
|
+
narrative: narrative ?? null,
|
|
87
|
+
findings: findingsData,
|
|
88
|
+
trend: trendData,
|
|
89
|
+
executiveSummary,
|
|
90
|
+
});
|
|
91
|
+
const findingsCount = findingsData.length;
|
|
92
|
+
const violationsCount = (report.policyEvaluation.topViolations || []).length;
|
|
93
|
+
const agentKeys = Object.keys(report.agentActivity.byAgent || {});
|
|
94
|
+
return `<!DOCTYPE html>
|
|
95
|
+
<html lang="en">
|
|
96
|
+
<head>
|
|
97
|
+
<meta charset="UTF-8">
|
|
98
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
99
|
+
<title>Shield Security Report - ${escapeHtml(report.hostname)}</title>
|
|
100
|
+
<style>
|
|
101
|
+
${CSS}
|
|
102
|
+
</style>
|
|
103
|
+
</head>
|
|
104
|
+
<body>
|
|
105
|
+
<script id="report-data" type="application/json">${jsonData.replace(/<\//g, '<\\/')}</script>
|
|
106
|
+
<div id="app">
|
|
107
|
+
<header class="header">
|
|
108
|
+
<div class="header-top">
|
|
109
|
+
<div class="header-left">
|
|
110
|
+
<h1 class="logo">Shield</h1>
|
|
111
|
+
<span class="header-sep">|</span>
|
|
112
|
+
<span class="header-label">Security Posture Report</span>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="header-right">
|
|
115
|
+
<span class="header-meta">${escapeHtml(report.hostname)} · ${escapeHtml(formatDate(report.periodStart))} to ${escapeHtml(formatDate(report.periodEnd))}</span>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
<nav class="nav-tabs" id="main-nav">
|
|
119
|
+
<button class="nav-tab active" data-page="overview">Overview</button>
|
|
120
|
+
<button class="nav-tab" data-page="findings">Findings${findingsCount > 0 ? ` <span class="nav-badge">${findingsCount}</span>` : ''}</button>
|
|
121
|
+
<button class="nav-tab" data-page="agents">Agents${agentKeys.length > 0 ? ` <span class="nav-badge">${agentKeys.length}</span>` : ''}</button>
|
|
122
|
+
<button class="nav-tab" data-page="violations">Violations${violationsCount > 0 ? ` <span class="nav-badge nav-badge-warn">${violationsCount}</span>` : ''}</button>
|
|
123
|
+
<button class="nav-tab" data-page="protection">Protection</button>
|
|
124
|
+
<button class="nav-tab" data-page="timeline">Timeline</button>
|
|
125
|
+
</nav>
|
|
126
|
+
</header>
|
|
127
|
+
|
|
128
|
+
<main class="main">
|
|
129
|
+
<div class="page active" id="page-overview"></div>
|
|
130
|
+
<div class="page" id="page-findings"></div>
|
|
131
|
+
<div class="page" id="page-agents"></div>
|
|
132
|
+
<div class="page" id="page-violations"></div>
|
|
133
|
+
<div class="page" id="page-protection"></div>
|
|
134
|
+
<div class="page" id="page-timeline"></div>
|
|
135
|
+
</main>
|
|
136
|
+
|
|
137
|
+
<footer class="footer">
|
|
138
|
+
<span>Generated ${escapeHtml(formatDate(report.generatedAt))} by OpenA2A Shield</span>
|
|
139
|
+
<span class="footer-sep"> | </span>
|
|
140
|
+
<a href="https://opena2a.org" target="_blank" rel="noopener noreferrer">opena2a.org</a>
|
|
141
|
+
</footer>
|
|
142
|
+
</div>
|
|
143
|
+
<script>
|
|
144
|
+
${JS}
|
|
145
|
+
</script>
|
|
146
|
+
</body>
|
|
147
|
+
</html>`;
|
|
148
|
+
}
|
|
149
|
+
function escapeHtml(str) {
|
|
150
|
+
return str
|
|
151
|
+
.replace(/&/g, '&')
|
|
152
|
+
.replace(/</g, '<')
|
|
153
|
+
.replace(/>/g, '>')
|
|
154
|
+
.replace(/"/g, '"')
|
|
155
|
+
.replace(/'/g, ''');
|
|
156
|
+
}
|
|
157
|
+
function formatDate(iso) {
|
|
158
|
+
try {
|
|
159
|
+
const d = new Date(iso);
|
|
160
|
+
if (isNaN(d.getTime()))
|
|
161
|
+
return iso;
|
|
162
|
+
return d.toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC');
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return iso;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// --- Embedded CSS ---
|
|
169
|
+
const CSS = `
|
|
170
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
171
|
+
:root{
|
|
172
|
+
--bg:#0f172a;--card:#1e293b;--card-border:#334155;--card-hover:#334155;
|
|
173
|
+
--primary:#06b6d4;--primary-dim:#0891b2;
|
|
174
|
+
--text:#e2e8f0;--muted:#94a3b8;--dim:#64748b;
|
|
175
|
+
--critical:#ef4444;--high:#f97316;--medium:#eab308;--low:#3b82f6;--info:#6b7280;
|
|
176
|
+
--green:#22c55e;--red:#ef4444;--amber:#f59e0b;
|
|
177
|
+
--radius:8px;--gap:16px;
|
|
178
|
+
--font:'JetBrains Mono','Fira Code','SF Mono',Menlo,Consolas,monospace;
|
|
179
|
+
}
|
|
180
|
+
body{font-family:var(--font);background:var(--bg);color:var(--text);line-height:1.6;font-size:14px}
|
|
181
|
+
a{color:var(--primary);text-decoration:none}
|
|
182
|
+
a:hover{text-decoration:underline}
|
|
183
|
+
|
|
184
|
+
.header{position:sticky;top:0;background:var(--bg);z-index:100;border-bottom:1px solid var(--card-border)}
|
|
185
|
+
.header-top{display:flex;justify-content:space-between;align-items:center;padding:12px 24px 0}
|
|
186
|
+
.header-left{display:flex;align-items:center;gap:12px}
|
|
187
|
+
.logo{font-size:20px;font-weight:700;color:var(--primary)}
|
|
188
|
+
.header-sep{color:var(--card-border)}
|
|
189
|
+
.header-label{color:var(--muted);font-size:14px}
|
|
190
|
+
.header-right{display:flex;align-items:center;gap:12px}
|
|
191
|
+
.header-meta{color:var(--dim);font-size:12px}
|
|
192
|
+
|
|
193
|
+
.nav-tabs{display:flex;gap:2px;padding:12px 24px 0;overflow-x:auto}
|
|
194
|
+
.nav-tab{background:transparent;border:none;border-bottom:2px solid transparent;padding:10px 16px;color:var(--muted);cursor:pointer;font-family:var(--font);font-size:13px;font-weight:600;transition:all .15s;white-space:nowrap;display:flex;align-items:center;gap:6px}
|
|
195
|
+
.nav-tab:hover{color:var(--text);border-bottom-color:var(--card-border)}
|
|
196
|
+
.nav-tab.active{color:var(--primary);border-bottom-color:var(--primary)}
|
|
197
|
+
.nav-badge{background:var(--card-border);color:var(--text);font-size:10px;padding:1px 6px;border-radius:10px;font-weight:700}
|
|
198
|
+
.nav-badge-warn{background:rgba(249,115,22,0.25);color:var(--high)}
|
|
199
|
+
|
|
200
|
+
.page{display:none}
|
|
201
|
+
.page.active{display:block}
|
|
202
|
+
|
|
203
|
+
.main{max-width:1280px;margin:0 auto;padding:24px}
|
|
204
|
+
|
|
205
|
+
.overview-top{display:grid;grid-template-columns:260px 1fr 1fr;gap:var(--gap);margin-bottom:24px}
|
|
206
|
+
@media(max-width:900px){.overview-top{grid-template-columns:1fr}}
|
|
207
|
+
|
|
208
|
+
.donut-legend{display:flex;flex-wrap:wrap;gap:12px;margin-top:12px}
|
|
209
|
+
.donut-legend-item{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted)}
|
|
210
|
+
.donut-legend-dot{width:10px;height:10px;border-radius:2px;flex-shrink:0}
|
|
211
|
+
|
|
212
|
+
.search-box{display:flex;margin-bottom:14px}
|
|
213
|
+
.search-input{flex:1;background:var(--bg);border:1px solid var(--card-border);border-radius:var(--radius);padding:8px 12px;color:var(--text);font-family:var(--font);font-size:12px;outline:none;transition:border-color .15s}
|
|
214
|
+
.search-input:focus{border-color:var(--primary)}
|
|
215
|
+
.search-input::placeholder{color:var(--dim)}
|
|
216
|
+
|
|
217
|
+
.finding-row{cursor:pointer;transition:background .15s}
|
|
218
|
+
.finding-row:hover td{background:rgba(6,182,212,0.04)}
|
|
219
|
+
.finding-expand{display:none;background:rgba(0,0,0,0.15)}
|
|
220
|
+
.finding-expand.open{display:table-row}
|
|
221
|
+
.finding-detail{padding:12px 20px}
|
|
222
|
+
.finding-detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
|
223
|
+
.finding-meta{font-size:11px;color:var(--muted);margin-bottom:4px;text-transform:uppercase;letter-spacing:0.03em}
|
|
224
|
+
.finding-val{font-size:12px;color:var(--text);margin-bottom:10px}
|
|
225
|
+
.finding-examples{margin-top:8px}
|
|
226
|
+
.finding-example{background:var(--bg);border:1px solid var(--card-border);border-radius:4px;padding:8px 12px;margin-bottom:6px;font-size:11px;overflow-x:auto}
|
|
227
|
+
|
|
228
|
+
.footer{text-align:center;padding:24px;color:var(--dim);font-size:12px;border-top:1px solid var(--card-border);margin-top:48px}
|
|
229
|
+
.footer-sep{color:var(--card-border);margin:0 4px}
|
|
230
|
+
|
|
231
|
+
/* Section headings */
|
|
232
|
+
.section-title{font-size:16px;font-weight:700;color:var(--text);margin:32px 0 16px;padding-bottom:8px;border-bottom:1px solid var(--card-border)}
|
|
233
|
+
.section-title:first-child{margin-top:0}
|
|
234
|
+
|
|
235
|
+
/* Cards */
|
|
236
|
+
.card{background:var(--card);border:1px solid var(--card-border);border-radius:var(--radius);padding:20px;margin-bottom:var(--gap)}
|
|
237
|
+
.card-title{font-size:13px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:12px}
|
|
238
|
+
|
|
239
|
+
/* Stats grid */
|
|
240
|
+
.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--gap);margin-bottom:24px}
|
|
241
|
+
.stat-card{background:var(--card);border:1px solid var(--card-border);border-radius:var(--radius);padding:20px;text-align:center}
|
|
242
|
+
.stat-value{font-size:28px;font-weight:700}
|
|
243
|
+
.stat-label{color:var(--muted);font-size:12px;margin-top:4px}
|
|
244
|
+
|
|
245
|
+
/* Posture gauge section */
|
|
246
|
+
.posture-section{display:grid;grid-template-columns:280px 1fr;gap:24px;margin-bottom:24px}
|
|
247
|
+
.gauge-card{background:var(--card);border:1px solid var(--card-border);border-radius:var(--radius);padding:24px;display:flex;flex-direction:column;align-items:center;justify-content:center}
|
|
248
|
+
.gauge-card .gauge-label{font-size:13px;color:var(--muted);margin-top:8px}
|
|
249
|
+
.gauge-card .gauge-trend{font-size:12px;color:var(--dim);margin-top:4px}
|
|
250
|
+
|
|
251
|
+
/* Severity bar chart */
|
|
252
|
+
.bar-chart{display:flex;flex-direction:column;gap:10px}
|
|
253
|
+
.bar-row{display:flex;align-items:center;gap:12px}
|
|
254
|
+
.bar-label{width:70px;text-align:right;font-size:12px;font-weight:600;text-transform:uppercase;flex-shrink:0}
|
|
255
|
+
.bar-track{flex:1;height:24px;background:rgba(255,255,255,0.05);border-radius:4px;overflow:hidden;position:relative}
|
|
256
|
+
.bar-fill{height:100%;border-radius:4px;transition:width .3s ease;display:flex;align-items:center;justify-content:flex-end;padding-right:8px;min-width:0}
|
|
257
|
+
.bar-count{font-size:11px;font-weight:700;color:white}
|
|
258
|
+
|
|
259
|
+
/* Tables */
|
|
260
|
+
.data-table{width:100%;border-collapse:collapse;font-size:13px}
|
|
261
|
+
.data-table th{text-align:left;padding:10px 12px;color:var(--muted);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;border-bottom:2px solid var(--card-border)}
|
|
262
|
+
.data-table td{padding:10px 12px;border-bottom:1px solid var(--card-border)}
|
|
263
|
+
.data-table tr:last-child td{border-bottom:none}
|
|
264
|
+
.data-table tr:hover td{background:rgba(255,255,255,0.02)}
|
|
265
|
+
|
|
266
|
+
/* Severity badges */
|
|
267
|
+
.sev-badge{display:inline-block;font-size:10px;font-weight:700;text-transform:uppercase;padding:2px 8px;border-radius:4px}
|
|
268
|
+
.sev-critical{background:rgba(239,68,68,0.15);color:var(--critical)}
|
|
269
|
+
.sev-high{background:rgba(249,115,22,0.15);color:var(--high)}
|
|
270
|
+
.sev-medium{background:rgba(234,179,8,0.15);color:var(--medium)}
|
|
271
|
+
.sev-low{background:rgba(59,130,246,0.15);color:var(--low)}
|
|
272
|
+
.sev-info{background:rgba(107,114,128,0.15);color:var(--info)}
|
|
273
|
+
|
|
274
|
+
/* Filter controls */
|
|
275
|
+
.filter-bar{display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap;align-items:center}
|
|
276
|
+
.filter-btn{background:transparent;border:1px solid var(--card-border);border-radius:var(--radius);padding:6px 12px;color:var(--muted);cursor:pointer;font-family:var(--font);font-size:12px;transition:all .2s}
|
|
277
|
+
.filter-btn:hover{border-color:var(--text);color:var(--text)}
|
|
278
|
+
.filter-btn.active{border-color:var(--primary);color:var(--primary)}
|
|
279
|
+
.filter-btn[data-sev="critical"].active{border-color:var(--critical);color:var(--critical)}
|
|
280
|
+
.filter-btn[data-sev="high"].active{border-color:var(--high);color:var(--high)}
|
|
281
|
+
.filter-btn[data-sev="medium"].active{border-color:var(--medium);color:var(--medium)}
|
|
282
|
+
.filter-btn[data-sev="low"].active{border-color:var(--low);color:var(--low)}
|
|
283
|
+
.filter-btn[data-sev="info"].active{border-color:var(--info);color:var(--info)}
|
|
284
|
+
|
|
285
|
+
.violation-count{color:var(--muted);font-size:12px;margin-left:auto}
|
|
286
|
+
|
|
287
|
+
/* Detail cards (runtime, creds, supply chain) */
|
|
288
|
+
.detail-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:var(--gap);margin-bottom:24px}
|
|
289
|
+
.detail-row{display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid rgba(255,255,255,0.05)}
|
|
290
|
+
.detail-row:last-child{border-bottom:none}
|
|
291
|
+
.detail-key{color:var(--muted);font-size:13px}
|
|
292
|
+
.detail-val{font-weight:600;font-size:13px}
|
|
293
|
+
|
|
294
|
+
/* Narrative */
|
|
295
|
+
.narrative-section{margin-top:24px}
|
|
296
|
+
.narrative-block{margin-bottom:16px}
|
|
297
|
+
.narrative-block h4{font-size:13px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:8px}
|
|
298
|
+
.narrative-text{color:var(--text);font-size:13px;line-height:1.7}
|
|
299
|
+
.narrative-list{list-style:none;padding:0}
|
|
300
|
+
.narrative-list li{padding:4px 0;color:var(--text);font-size:13px}
|
|
301
|
+
.narrative-list li::before{content:"--";color:var(--dim);margin-right:8px}
|
|
302
|
+
.narrative-concern li::before{color:var(--amber)}
|
|
303
|
+
.narrative-highlight li::before{color:var(--green)}
|
|
304
|
+
.narrative-rec li::before{color:var(--primary)}
|
|
305
|
+
|
|
306
|
+
/* Provider list */
|
|
307
|
+
.provider-list{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}
|
|
308
|
+
.provider-tag{background:rgba(6,182,212,0.1);border:1px solid rgba(6,182,212,0.3);border-radius:4px;padding:2px 10px;font-size:12px;color:var(--primary)}
|
|
309
|
+
|
|
310
|
+
/* Empty state */
|
|
311
|
+
.empty-state{color:var(--dim);font-size:13px;padding:16px 0;text-align:center}
|
|
312
|
+
|
|
313
|
+
/* Posture factors */
|
|
314
|
+
.factor-row{display:flex;align-items:center;gap:12px;padding:6px 0}
|
|
315
|
+
.factor-name{width:100px;font-size:12px;color:var(--muted)}
|
|
316
|
+
.factor-bar{flex:1;height:8px;background:rgba(255,255,255,0.05);border-radius:4px;overflow:hidden}
|
|
317
|
+
.factor-fill{height:100%;border-radius:4px;background:var(--primary)}
|
|
318
|
+
.factor-score{width:40px;text-align:right;font-size:12px;font-weight:600}
|
|
319
|
+
.factor-detail{font-size:11px;color:var(--dim);width:160px}
|
|
320
|
+
|
|
321
|
+
/* Active/inactive indicator */
|
|
322
|
+
.status-active{color:var(--green);font-weight:600}
|
|
323
|
+
.status-inactive{color:var(--red);font-weight:600}
|
|
324
|
+
|
|
325
|
+
/* Compliance badges */
|
|
326
|
+
.badge-owasp{background:#f59e0b;color:#000;padding:2px 6px;border-radius:3px;font-size:10px;font-weight:700;display:inline-block;margin:1px 2px}
|
|
327
|
+
.badge-mitre{background:#8b5cf6;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px;font-weight:700;display:inline-block;margin:1px 2px}
|
|
328
|
+
.finding-id{font-family:var(--font);font-size:11px;color:var(--primary);font-weight:600;cursor:pointer}
|
|
329
|
+
.finding-id:hover{text-decoration:underline}
|
|
330
|
+
.finding-highlight td{animation:highlightPulse 1.5s ease-out}
|
|
331
|
+
@keyframes highlightPulse{0%{background:rgba(6,182,212,0.2)}100%{background:transparent}}
|
|
332
|
+
|
|
333
|
+
/* Remediation command */
|
|
334
|
+
.remediation-cmd{display:flex;align-items:center;gap:6px}
|
|
335
|
+
.remediation-code{background:rgba(255,255,255,0.05);border:1px solid var(--card-border);border-radius:4px;padding:3px 8px;font-size:11px;font-family:var(--font);color:var(--text);max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
336
|
+
.copy-btn{background:transparent;border:1px solid var(--card-border);border-radius:4px;padding:3px 6px;color:var(--muted);cursor:pointer;font-family:var(--font);font-size:10px;transition:all .2s;flex-shrink:0}
|
|
337
|
+
.copy-btn:hover{border-color:var(--primary);color:var(--primary)}
|
|
338
|
+
.copy-btn.copied{border-color:var(--green);color:var(--green)}
|
|
339
|
+
|
|
340
|
+
/* Posture checklist */
|
|
341
|
+
.checklist{display:grid;gap:var(--gap);margin-bottom:24px}
|
|
342
|
+
.check-item{background:var(--card);border:1px solid var(--card-border);border-radius:var(--radius);padding:20px;display:flex;gap:16px;align-items:flex-start}
|
|
343
|
+
.check-status{flex-shrink:0;width:100px;text-align:center}
|
|
344
|
+
.check-badge{display:inline-block;padding:4px 12px;border-radius:4px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.03em}
|
|
345
|
+
.check-badge-active{background:rgba(34,197,94,0.15);color:var(--green)}
|
|
346
|
+
.check-badge-inactive{background:rgba(239,68,68,0.15);color:var(--red)}
|
|
347
|
+
.check-badge-monitor{background:rgba(245,158,11,0.15);color:var(--amber)}
|
|
348
|
+
.check-body{flex:1;min-width:0}
|
|
349
|
+
.check-title{font-size:14px;font-weight:600;color:var(--text);margin-bottom:4px}
|
|
350
|
+
.check-desc{font-size:12px;color:var(--muted);margin-bottom:8px}
|
|
351
|
+
.check-metric{font-size:12px;color:var(--primary);font-weight:600}
|
|
352
|
+
.check-remediation{display:flex;align-items:center;gap:8px;margin-top:8px}
|
|
353
|
+
.check-cmd{background:rgba(255,255,255,0.05);border:1px solid var(--card-border);border-radius:4px;padding:6px 12px;font-size:12px;font-family:var(--font);color:var(--text)}
|
|
354
|
+
|
|
355
|
+
/* Executive summary */
|
|
356
|
+
.exec-summary{background:var(--card);border:1px solid var(--card-border);border-radius:var(--radius);padding:20px;margin-bottom:24px}
|
|
357
|
+
.exec-summary-title{font-size:13px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:12px}
|
|
358
|
+
.exec-summary-text{color:var(--text);font-size:13px;line-height:1.8}
|
|
359
|
+
|
|
360
|
+
/* Trend indicator */
|
|
361
|
+
.trend-indicator{display:inline-flex;align-items:center;gap:6px;font-size:13px;margin-top:8px}
|
|
362
|
+
.trend-improving{color:var(--green)}
|
|
363
|
+
.trend-declining{color:var(--red)}
|
|
364
|
+
.trend-stable{color:var(--muted)}
|
|
365
|
+
.trend-delta{font-weight:700}
|
|
366
|
+
|
|
367
|
+
@media(max-width:768px){
|
|
368
|
+
.posture-section{grid-template-columns:1fr}
|
|
369
|
+
.detail-grid{grid-template-columns:1fr}
|
|
370
|
+
.header-top{flex-direction:column;gap:8px}
|
|
371
|
+
.header-right{width:100%}
|
|
372
|
+
.stats-grid{grid-template-columns:repeat(2,1fr)}
|
|
373
|
+
.finding-detail-grid{grid-template-columns:1fr}
|
|
374
|
+
.nav-tabs{gap:0}
|
|
375
|
+
}
|
|
376
|
+
`;
|
|
377
|
+
// --- Embedded JavaScript ---
|
|
378
|
+
const JS = `
|
|
379
|
+
(function() {
|
|
380
|
+
'use strict';
|
|
381
|
+
var raw = JSON.parse(document.getElementById('report-data').textContent);
|
|
382
|
+
var report = raw.report;
|
|
383
|
+
var narrative = raw.narrative;
|
|
384
|
+
var findings = raw.findings || [];
|
|
385
|
+
var trend = raw.trend || null;
|
|
386
|
+
var executiveSummary = raw.executiveSummary || '';
|
|
387
|
+
var currentPage = 'overview';
|
|
388
|
+
var activeViolationFilters = new Set(['critical','high','medium','low','info']);
|
|
389
|
+
var pagesRendered = {};
|
|
390
|
+
|
|
391
|
+
function init() { renderPage('overview'); bindNav(); }
|
|
392
|
+
|
|
393
|
+
function esc(s) { if (!s) return ''; var d = document.createElement('div'); d.textContent = String(s); return d.innerHTML; }
|
|
394
|
+
function formatTs(iso) { if (!iso) return '--'; try { var d = new Date(iso); if (isNaN(d.getTime())) return iso; return d.toISOString().replace('T',' ').replace(/\\.\\d+Z$/,' UTC'); } catch(e) { return iso; } }
|
|
395
|
+
function scoreColor(s) { return s >= 90 ? 'var(--green)' : s >= 70 ? 'var(--primary)' : s >= 50 ? 'var(--medium)' : 'var(--red)'; }
|
|
396
|
+
function statCard(v, l, c) { return '<div class="stat-card"><div class="stat-value" style="color:'+c+'">'+v+'</div><div class="stat-label">'+l+'</div></div>'; }
|
|
397
|
+
|
|
398
|
+
function bindNav() {
|
|
399
|
+
document.getElementById('main-nav').addEventListener('click', function(e) {
|
|
400
|
+
var btn = e.target.closest('.nav-tab'); if (!btn) return;
|
|
401
|
+
var pg = btn.dataset.page; if (!pg || pg === currentPage) return;
|
|
402
|
+
var tabs = document.querySelectorAll('.nav-tab');
|
|
403
|
+
for (var i=0;i<tabs.length;i++) tabs[i].classList.toggle('active', tabs[i].dataset.page===pg);
|
|
404
|
+
var pages = document.querySelectorAll('.page');
|
|
405
|
+
for (var i=0;i<pages.length;i++) pages[i].classList.toggle('active', pages[i].id==='page-'+pg);
|
|
406
|
+
currentPage = pg; renderPage(pg);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function renderPage(pg) {
|
|
411
|
+
if (pagesRendered[pg]) return; pagesRendered[pg] = true;
|
|
412
|
+
var el = document.getElementById('page-'+pg);
|
|
413
|
+
switch(pg) {
|
|
414
|
+
case 'overview': el.innerHTML = renderOverview(); break;
|
|
415
|
+
case 'findings': el.innerHTML = renderFindings(); bindFindingExpand(); break;
|
|
416
|
+
case 'agents': el.innerHTML = renderAgents(); break;
|
|
417
|
+
case 'violations': el.innerHTML = renderViolations(); bindViolationFilters(); break;
|
|
418
|
+
case 'protection': el.innerHTML = renderProtection(); break;
|
|
419
|
+
case 'timeline': el.innerHTML = renderTimeline(); break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// --- Gauge SVG ---
|
|
424
|
+
function gaugeCircle(score) {
|
|
425
|
+
var sz=170,cx=sz/2,cy=sz/2,r=65,sw=10,circ=2*Math.PI*r;
|
|
426
|
+
var pct=Math.max(0,Math.min(100,score))/100,dash=pct*circ,gap=circ-dash;
|
|
427
|
+
var clr = score>=90?'#22c55e':score>=70?'#06b6d4':score>=50?'#eab308':'#ef4444';
|
|
428
|
+
var g = report.posture.grade||'';
|
|
429
|
+
var s='<svg width="'+sz+'" height="'+sz+'" viewBox="0 0 '+sz+' '+sz+'">';
|
|
430
|
+
s+='<circle cx="'+cx+'" cy="'+cy+'" r="'+r+'" fill="none" stroke="rgba(255,255,255,0.05)" stroke-width="'+sw+'"/>';
|
|
431
|
+
s+='<circle cx="'+cx+'" cy="'+cy+'" r="'+r+'" fill="none" stroke="'+clr+'" stroke-width="'+sw+'" stroke-dasharray="'+dash+' '+gap+'" stroke-dashoffset="'+(circ*0.25)+'" stroke-linecap="round" transform="rotate(-90 '+cx+' '+cy+')"/>';
|
|
432
|
+
s+='<text x="'+cx+'" y="'+(cy-6)+'" text-anchor="middle" dominant-baseline="middle" font-size="32" font-weight="700" fill="'+clr+'" font-family="var(--font)">'+score+'</text>';
|
|
433
|
+
s+='<text x="'+cx+'" y="'+(cy+20)+'" text-anchor="middle" dominant-baseline="middle" font-size="14" font-weight="600" fill="'+clr+'" font-family="var(--font)">Grade '+esc(g)+'</text>';
|
|
434
|
+
s+='</svg>'; return s;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function trendHtml() {
|
|
438
|
+
if (!trend) return '';
|
|
439
|
+
var cls='trend-'+trend.direction;
|
|
440
|
+
var arrow=trend.direction==='improving'?'▲':trend.direction==='declining'?'▼':'▶';
|
|
441
|
+
var sign=trend.delta>0?'+':'';
|
|
442
|
+
return '<div class="trend-indicator '+cls+'">'+arrow+' <span class="trend-delta">'+sign+trend.delta+'</span> from '+trend.previousScore+'/'+esc(trend.previousGrade)+' ('+trend.periodDays+'d)</div>';
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// --- Donut chart ---
|
|
446
|
+
function donutChart() {
|
|
447
|
+
var counts={critical:0,high:0,medium:0,low:0,info:0};
|
|
448
|
+
var violations=report.policyEvaluation.topViolations||[];
|
|
449
|
+
for(var i=0;i<violations.length;i++) counts[violations[i].severity]=(counts[violations[i].severity]||0)+violations[i].count;
|
|
450
|
+
for(var i=0;i<findings.length;i++){var s=findings[i].finding.severity;counts[s]=(counts[s]||0)+findings[i].count;}
|
|
451
|
+
var total=counts.critical+counts.high+counts.medium+counts.low+counts.info;
|
|
452
|
+
if(total===0) return '<div class="empty-state">No findings in this period.</div>';
|
|
453
|
+
var sz=150,cx=sz/2,cy=sz/2,r=50,sw=18,circ=2*Math.PI*r;
|
|
454
|
+
var colors={critical:'#ef4444',high:'#f97316',medium:'#eab308',low:'#3b82f6',info:'#6b7280'};
|
|
455
|
+
var order=['critical','high','medium','low','info'];
|
|
456
|
+
var off=0,svg='<svg width="'+sz+'" height="'+sz+'" viewBox="0 0 '+sz+' '+sz+'">';
|
|
457
|
+
for(var j=0;j<order.length;j++){var sv=order[j],c=counts[sv]||0;if(!c)continue;var p=c/total,d=p*circ,g=circ-d;
|
|
458
|
+
svg+='<circle cx="'+cx+'" cy="'+cy+'" r="'+r+'" fill="none" stroke="'+colors[sv]+'" stroke-width="'+sw+'" stroke-dasharray="'+d+' '+g+'" stroke-dashoffset="'+(-off+circ*0.25)+'" transform="rotate(-90 '+cx+' '+cy+')"/>';off+=d;}
|
|
459
|
+
svg+='<text x="'+cx+'" y="'+cy+'" text-anchor="middle" dominant-baseline="middle" font-size="18" font-weight="700" fill="var(--text)" font-family="var(--font)">'+total+'</text></svg>';
|
|
460
|
+
var leg='<div class="donut-legend" style="flex-direction:column">';
|
|
461
|
+
for(var k=0;k<order.length;k++){var sv=order[k];if(!counts[sv])continue;leg+='<div class="donut-legend-item"><div class="donut-legend-dot" style="background:'+colors[sv]+'"></div>'+sv.charAt(0).toUpperCase()+sv.slice(1)+': '+counts[sv]+'</div>';}
|
|
462
|
+
leg+='</div>';
|
|
463
|
+
return '<div style="display:flex;align-items:center;gap:16px;flex-wrap:wrap">'+svg+leg+'</div>';
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function factorsHtml() {
|
|
467
|
+
var factors=report.posture.factors||[];if(!factors.length) return '';
|
|
468
|
+
var h='<div class="card"><div class="card-title">Score Factors</div>';
|
|
469
|
+
for(var i=0;i<factors.length;i++){var f=factors[i];var bc=f.score>=70?'var(--green)':f.score>=40?'var(--medium)':'var(--red)';
|
|
470
|
+
h+='<div class="factor-row"><span class="factor-name">'+esc(f.name)+(f.weight?' ('+Math.round(f.weight*100)+'%)':'')+'</span><div class="factor-bar"><div class="factor-fill" style="width:'+f.score+'%;background:'+bc+'"></div></div><span class="factor-score">'+f.score+'</span><span class="factor-detail">'+esc(f.detail)+'</span></div>';}
|
|
471
|
+
h+='</div>'; return h;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// ======================== OVERVIEW ========================
|
|
475
|
+
function renderOverview() {
|
|
476
|
+
var h='';
|
|
477
|
+
h+='<div class="stats-grid">';
|
|
478
|
+
h+=statCard(report.posture.score+'/100','Score',scoreColor(report.posture.score));
|
|
479
|
+
h+=statCard('Grade '+(report.posture.grade||'--'),'Posture',scoreColor(report.posture.score));
|
|
480
|
+
h+=statCard(report.agentActivity.totalSessions,'Sessions','var(--primary)');
|
|
481
|
+
h+=statCard(report.agentActivity.totalActions,'Events','var(--text)');
|
|
482
|
+
h+=statCard(report.policyEvaluation.monitored,'Monitored','var(--muted)');
|
|
483
|
+
h+=statCard(report.policyEvaluation.blocked,'Blocked',report.policyEvaluation.blocked>0?'var(--red)':'var(--text)');
|
|
484
|
+
h+='</div>';
|
|
485
|
+
|
|
486
|
+
h+='<h2 class="section-title">Posture Score</h2>';
|
|
487
|
+
h+='<div class="overview-top">';
|
|
488
|
+
h+='<div class="gauge-card">'+gaugeCircle(report.posture.score)+trendHtml()+'</div>';
|
|
489
|
+
h+='<div class="card"><div class="card-title">Severity Breakdown</div>'+donutChart()+'</div>';
|
|
490
|
+
h+=factorsHtml();
|
|
491
|
+
h+='</div>';
|
|
492
|
+
|
|
493
|
+
if(executiveSummary){h+='<div class="exec-summary"><div class="exec-summary-title">Executive Summary</div><div class="exec-summary-text">'+esc(executiveSummary)+'</div></div>';}
|
|
494
|
+
|
|
495
|
+
if(findings.length>0){
|
|
496
|
+
h+='<h2 class="section-title">'+(findings.length>5?'Top Findings':'Findings')+'</h2><div class="card"><table class="data-table"><thead><tr><th>ID</th><th>Title</th><th>Severity</th><th>Count</th><th>OWASP</th><th>MITRE</th></tr></thead><tbody>';
|
|
497
|
+
var top=findings.slice(0,5);
|
|
498
|
+
for(var i=0;i<top.length;i++){var f=top[i];h+='<tr><td><span class="finding-id" onclick="navigateToFinding(\\''+esc(f.finding.id)+'\\')">'+esc(f.finding.id)+'</span></td><td>'+esc(f.finding.title)+'</td><td><span class="sev-badge sev-'+esc(f.finding.severity)+'">'+esc(f.finding.severity)+'</span></td><td>'+f.count+'</td><td><span class="badge-owasp">'+esc(f.finding.owaspAgentic)+'</span></td><td><span class="badge-mitre">'+esc(f.finding.mitreAtlas)+'</span></td></tr>';}
|
|
499
|
+
h+='</tbody></table>';
|
|
500
|
+
if(findings.length>5) h+='<div style="text-align:center;padding:8px;color:var(--dim);font-size:11px;cursor:pointer" onclick="document.querySelector(\\'.nav-tab[data-page=findings]\\').click()">View all '+findings.length+' findings --></div>';
|
|
501
|
+
h+='</div>';
|
|
502
|
+
}
|
|
503
|
+
return h;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ======================== FINDINGS ========================
|
|
507
|
+
function renderFindings() {
|
|
508
|
+
if(!findings.length) return '<h2 class="section-title">Classified Findings</h2><div class="card"><div class="empty-state">No security findings classified in this period.</div></div>';
|
|
509
|
+
var h='<h2 class="section-title">Classified Findings ('+findings.length+')</h2>';
|
|
510
|
+
h+='<div class="search-box"><input type="text" class="search-input" id="findings-search" placeholder="Search findings by ID, title, OWASP, MITRE..." oninput="window._filterFindings(this.value)"></div>';
|
|
511
|
+
h+='<div class="card"><table class="data-table" id="findings-table"><thead><tr><th>ID</th><th>Title</th><th>Severity</th><th>Count</th><th>OWASP</th><th>MITRE</th><th>Remediation</th></tr></thead><tbody>';
|
|
512
|
+
for(var i=0;i<findings.length;i++){var f=findings[i];
|
|
513
|
+
h+='<tr class="finding-row" data-idx="'+i+'" data-search="'+esc((f.finding.id+' '+f.finding.title+' '+f.finding.owaspAgentic+' '+f.finding.mitreAtlas+' '+f.finding.category).toLowerCase())+'">';
|
|
514
|
+
h+='<td><span class="finding-id">'+esc(f.finding.id)+'</span></td><td>'+esc(f.finding.title)+'</td>';
|
|
515
|
+
h+='<td><span class="sev-badge sev-'+esc(f.finding.severity)+'">'+esc(f.finding.severity)+'</span></td><td>'+f.count+'</td>';
|
|
516
|
+
h+='<td><span class="badge-owasp">'+esc(f.finding.owaspAgentic)+'</span></td><td><span class="badge-mitre">'+esc(f.finding.mitreAtlas)+'</span></td>';
|
|
517
|
+
h+='<td><div class="remediation-cmd"><code class="remediation-code" title="'+esc(f.finding.remediation)+'">'+esc(f.finding.remediation)+'</code><button class="copy-btn" data-cmd="'+esc(f.finding.remediation)+'" onclick="event.stopPropagation();copyCmd(this)">Copy</button></div></td></tr>';
|
|
518
|
+
h+='<tr class="finding-expand" id="finding-detail-'+i+'"><td colspan="7"><div class="finding-detail"><div class="finding-detail-grid">';
|
|
519
|
+
h+='<div><div class="finding-meta">Description</div><div class="finding-val">'+esc(f.finding.description)+'</div></div>';
|
|
520
|
+
h+='<div><div class="finding-meta">Category</div><div class="finding-val">'+esc(f.finding.category)+'</div><div class="finding-meta" style="margin-top:8px">Time Range</div><div class="finding-val">'+esc(formatTs(f.firstSeen))+' to '+esc(formatTs(f.lastSeen))+'</div></div></div>';
|
|
521
|
+
if(f.examples&&f.examples.length>0){h+='<div class="finding-examples"><div class="finding-meta">Event Examples ('+f.examples.length+')</div>';
|
|
522
|
+
for(var ei=0;ei<f.examples.length;ei++){var ex=f.examples[ei];h+='<div class="finding-example"><span style="color:var(--dim)">'+esc(formatTs(ex.timestamp))+'</span> <span style="color:var(--primary)">'+esc(ex.source)+'</span>: '+esc(ex.action)+' -> '+esc(ex.target)+' [<span class="sev-badge sev-'+esc(ex.severity)+'" style="font-size:9px;padding:1px 4px">'+esc(ex.severity)+'</span>]</div>';}
|
|
523
|
+
h+='</div>';}
|
|
524
|
+
h+='</div></td></tr>';}
|
|
525
|
+
h+='</tbody></table></div>';return h;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function bindFindingExpand(){document.addEventListener('click',function(e){var row=e.target.closest('.finding-row');if(!row)return;var idx=row.dataset.idx;if(idx===undefined)return;var det=document.getElementById('finding-detail-'+idx);if(det)det.classList.toggle('open');});}
|
|
529
|
+
window._filterFindings=function(q){q=q.toLowerCase().trim();var rows=document.querySelectorAll('#findings-table .finding-row');var dets=document.querySelectorAll('#findings-table .finding-expand');for(var i=0;i<rows.length;i++){var m=!q||(rows[i].dataset.search||'').indexOf(q)>=0;rows[i].style.display=m?'':'none';if(dets[i]){dets[i].style.display='none';dets[i].classList.remove('open');}}};
|
|
530
|
+
|
|
531
|
+
// ======================== AGENTS ========================
|
|
532
|
+
function renderAgents() {
|
|
533
|
+
var agents=report.agentActivity.byAgent;var keys=Object.keys(agents);
|
|
534
|
+
if(!keys.length) return '<h2 class="section-title">Agent Activity</h2><div class="card"><div class="empty-state">No agent activity recorded.</div></div>';
|
|
535
|
+
var h='<h2 class="section-title">Agent Activity ('+keys.length+' agents)</h2>';
|
|
536
|
+
h+='<div class="stats-grid">'+statCard(keys.length,'Agents','var(--primary)')+statCard(report.agentActivity.totalSessions,'Sessions','var(--text)')+statCard(report.agentActivity.totalActions,'Total Actions','var(--text)')+'</div>';
|
|
537
|
+
h+='<div class="card"><table class="data-table"><thead><tr><th>Agent</th><th>Sessions</th><th>Actions</th><th>First Seen</th><th>Last Seen</th><th>Top Actions</th></tr></thead><tbody>';
|
|
538
|
+
for(var i=0;i<keys.length;i++){var name=keys[i],a=agents[name];
|
|
539
|
+
var topActs=(a.topActions||[]).slice(0,4).map(function(ta){return '<span style="color:var(--primary)">'+esc(ta.action)+'</span> <span style="color:var(--dim)">('+ta.count+')</span>';}).join(', ');
|
|
540
|
+
h+='<tr><td style="font-weight:600;color:var(--primary)">'+esc(name)+'</td><td>'+a.sessions+'</td><td>'+a.actions+'</td><td style="font-size:11px;color:var(--dim)">'+esc(formatTs(a.firstSeen))+'</td><td style="font-size:11px;color:var(--dim)">'+esc(formatTs(a.lastSeen))+'</td><td style="font-size:11px">'+(topActs||'--')+'</td></tr>';}
|
|
541
|
+
h+='</tbody></table></div>';return h;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// ======================== VIOLATIONS ========================
|
|
545
|
+
function renderViolations() {
|
|
546
|
+
var violations=report.policyEvaluation.topViolations||[];
|
|
547
|
+
if(!violations.length) return '<h2 class="section-title">Policy Violations</h2><div class="card"><div class="empty-state">No policy violations recorded.</div></div>';
|
|
548
|
+
var h='<h2 class="section-title">Policy Violations</h2>';
|
|
549
|
+
h+='<div class="stats-grid">'+statCard(violations.length,'Violations','var(--red)')+statCard(report.policyEvaluation.monitored,'Monitored','var(--muted)')+statCard(report.policyEvaluation.blocked,'Blocked',report.policyEvaluation.blocked>0?'var(--red)':'var(--text)')+'</div>';
|
|
550
|
+
h+='<div class="card">';
|
|
551
|
+
h+='<div class="filter-bar" id="violation-filters"><button class="filter-btn active" data-sev="all">All</button><button class="filter-btn active" data-sev="critical">Critical</button><button class="filter-btn active" data-sev="high">High</button><button class="filter-btn active" data-sev="medium">Medium</button><button class="filter-btn active" data-sev="low">Low</button><button class="filter-btn active" data-sev="info">Info</button><span class="violation-count" id="violation-count">'+violations.length+' violations</span></div>';
|
|
552
|
+
h+='<div class="search-box"><input type="text" class="search-input" id="violations-search" placeholder="Search violations..." oninput="window._applyVF()"></div>';
|
|
553
|
+
h+='<table class="data-table" id="violations-table"><thead><tr><th>Finding</th><th>Action</th><th>Target</th><th>Agent</th><th>Count</th><th>Severity</th><th>Compliance</th><th>Remediation</th></tr></thead><tbody>';
|
|
554
|
+
for(var i=0;i<violations.length;i++){var v=violations[i];
|
|
555
|
+
h+='<tr class="violation-row" data-severity="'+esc(v.severity)+'" data-search="'+esc(((v.findingId||'')+' '+v.action+' '+v.target+' '+v.agent).toLowerCase())+'">';
|
|
556
|
+
h+='<td>'+(v.findingId?'<span class="finding-id" onclick="event.stopPropagation();navigateToFinding(\\''+esc(v.findingId)+'\\')">'+esc(v.findingId)+'</span>':'<span style="color:var(--dim)">--</span>')+'</td>';
|
|
557
|
+
h+='<td>'+esc(v.action)+'</td><td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="'+esc(v.target)+'">'+esc(v.target)+'</td><td>'+esc(v.agent)+'</td><td>'+v.count+'</td>';
|
|
558
|
+
h+='<td><span class="sev-badge sev-'+esc(v.severity)+'">'+esc(v.severity)+'</span></td><td>';
|
|
559
|
+
if(v.compliance&&v.compliance.length>0){for(var ci=0;ci<v.compliance.length;ci++){var tag=v.compliance[ci];if(tag.indexOf('ASI')===0)h+='<span class="badge-owasp">'+esc(tag)+'</span>';else if(tag.indexOf('AML')===0)h+='<span class="badge-mitre">'+esc(tag)+'</span>';}}else h+='--';
|
|
560
|
+
h+='</td><td>';
|
|
561
|
+
if(v.remediationCommand){h+='<div class="remediation-cmd"><code class="remediation-code" title="'+esc(v.remediationCommand)+'">'+esc(v.remediationCommand)+'</code><button class="copy-btn" data-cmd="'+esc(v.remediationCommand)+'" onclick="copyCmd(this)">Copy</button></div>';}else h+=esc(v.recommendation);
|
|
562
|
+
h+='</td></tr>';}
|
|
563
|
+
h+='</tbody></table></div>';return h;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function bindViolationFilters(){var bar=document.getElementById('violation-filters');if(!bar)return;bar.addEventListener('click',function(e){if(!e.target.classList.contains('filter-btn'))return;var sev=e.target.dataset.sev;if(sev==='all'){activeViolationFilters=new Set(['critical','high','medium','low','info']);var btns=bar.querySelectorAll('.filter-btn');for(var b=0;b<btns.length;b++)btns[b].classList.add('active');}else{if(activeViolationFilters.has(sev))activeViolationFilters.delete(sev);else activeViolationFilters.add(sev);e.target.classList.toggle('active');var ab=bar.querySelector('.filter-btn[data-sev="all"]');if(ab)ab.classList.toggle('active',activeViolationFilters.size===5);}window._applyVF();});}
|
|
567
|
+
window._applyVF=function(){var rows=document.querySelectorAll('.violation-row');var q=((document.getElementById('violations-search')||{}).value||'').toLowerCase().trim();var vis=0;for(var i=0;i<rows.length;i++){var sv=rows[i].dataset.severity;var sm=activeViolationFilters.has(sv);var qm=!q||(rows[i].dataset.search||'').indexOf(q)>=0;var show=sm&&qm;rows[i].style.display=show?'':'none';if(show)vis++;}var c=document.getElementById('violation-count');if(c)c.textContent=vis+' of '+rows.length+' violations';};
|
|
568
|
+
|
|
569
|
+
// ======================== PROTECTION ========================
|
|
570
|
+
function renderProtection() {
|
|
571
|
+
var h='<h2 class="section-title">Posture Checklist</h2>';
|
|
572
|
+
var rt=report.runtimeProtection;
|
|
573
|
+
var ci=report.configIntegrity;
|
|
574
|
+
var cred=report.credentialExposure;
|
|
575
|
+
var pe=report.policyEvaluation;
|
|
576
|
+
|
|
577
|
+
h+='<div class="checklist">';
|
|
578
|
+
|
|
579
|
+
// Runtime Guard (ARP)
|
|
580
|
+
h+='<div class="check-item"><div class="check-status"><span class="check-badge '+(rt.arpActive?'check-badge-active':'check-badge-inactive')+'">'+(rt.arpActive?'Active':'Not Configured')+'</span></div><div class="check-body"><div class="check-title">Runtime Guard (ARP)</div><div class="check-desc">Monitors process spawns, network connections, and anomalous agent behavior in real time.</div>';
|
|
581
|
+
if(rt.arpActive){h+='<div class="check-metric">'+rt.processesSpawned+' processes, '+rt.networkConnections+' connections, '+rt.anomalies+' anomalies</div>';}
|
|
582
|
+
else{h+='<div class="check-remediation"><code class="check-cmd">opena2a shield init</code><button class="copy-btn" data-cmd="opena2a shield init" onclick="copyCmd(this)">Copy</button></div>';}
|
|
583
|
+
h+='</div></div>';
|
|
584
|
+
|
|
585
|
+
// Config Integrity
|
|
586
|
+
var ciActive=ci.filesMonitored>0;
|
|
587
|
+
var ciTampered=ci.tamperedFiles&&ci.tamperedFiles.length>0;
|
|
588
|
+
h+='<div class="check-item"><div class="check-status"><span class="check-badge '+(ciActive?(ciTampered?'check-badge-monitor':'check-badge-active'):'check-badge-inactive')+'">'+(ciActive?(ciTampered?'Tampered':'Active'):'Not Configured')+'</span></div><div class="check-body"><div class="check-title">Config Integrity</div><div class="check-desc">Cryptographically signs configuration files and detects unauthorized modifications.</div>';
|
|
589
|
+
if(ciActive){h+='<div class="check-metric">'+ci.filesMonitored+' file'+(ci.filesMonitored!==1?'s':'')+' signed'+(ciTampered?', <span style="color:var(--red)">'+ci.tamperedFiles.length+' tampered</span>':'')+'</div>';}
|
|
590
|
+
else{h+='<div class="check-remediation"><code class="check-cmd">opena2a guard sign</code><button class="copy-btn" data-cmd="opena2a guard sign" onclick="copyCmd(this)">Copy</button></div>';}
|
|
591
|
+
h+='</div></div>';
|
|
592
|
+
|
|
593
|
+
// Credential Protection
|
|
594
|
+
var credActive=cred.accessAttempts>0;
|
|
595
|
+
h+='<div class="check-item"><div class="check-status"><span class="check-badge '+(credActive?'check-badge-active':'check-badge-inactive')+'">'+(credActive?'Active':'Not Configured')+'</span></div><div class="check-body"><div class="check-title">Credential Protection</div><div class="check-desc">Scans source code for hardcoded API keys, tokens, and secrets before they reach version control.</div>';
|
|
596
|
+
if(credActive){h+='<div class="check-metric">'+cred.accessAttempts+' scan'+(cred.accessAttempts!==1?'s':'')+', '+cred.uniqueCredentials+' unique credential'+(cred.uniqueCredentials!==1?'s':'')+'</div>';}
|
|
597
|
+
else{h+='<div class="check-remediation"><code class="check-cmd">opena2a protect --dir .</code><button class="copy-btn" data-cmd="opena2a protect --dir ." onclick="copyCmd(this)">Copy</button></div>';}
|
|
598
|
+
h+='</div></div>';
|
|
599
|
+
|
|
600
|
+
// Policy Enforcement
|
|
601
|
+
var polActive=pe.blocked>0;
|
|
602
|
+
var polMonitor=pe.monitored>0&&pe.blocked===0;
|
|
603
|
+
h+='<div class="check-item"><div class="check-status"><span class="check-badge '+(polActive?'check-badge-active':polMonitor?'check-badge-monitor':'check-badge-inactive')+'">'+(polActive?'Active':polMonitor?'Monitor Only':'Not Configured')+'</span></div><div class="check-body"><div class="check-title">Policy Enforcement</div><div class="check-desc">Evaluates agent actions against security policies and blocks unauthorized operations.</div>';
|
|
604
|
+
if(polActive){h+='<div class="check-metric">'+pe.blocked+' action'+(pe.blocked!==1?'s':'')+' blocked</div>';}
|
|
605
|
+
else if(polMonitor){h+='<div class="check-metric">'+pe.monitored+' action'+(pe.monitored!==1?'s':'')+' monitored (not blocking)</div><div class="check-remediation"><code class="check-cmd">opena2a shield policy --enforce</code><button class="copy-btn" data-cmd="opena2a shield policy --enforce" onclick="copyCmd(this)">Copy</button></div>';}
|
|
606
|
+
else{h+='<div class="check-remediation"><code class="check-cmd">opena2a shield policy --enforce</code><button class="copy-btn" data-cmd="opena2a shield policy --enforce" onclick="copyCmd(this)">Copy</button></div>';}
|
|
607
|
+
h+='</div></div>';
|
|
608
|
+
|
|
609
|
+
h+='</div>';
|
|
610
|
+
|
|
611
|
+
// Detail cards below the checklist
|
|
612
|
+
h+='<h2 class="section-title">Protection Details</h2><div class="detail-grid">';
|
|
613
|
+
h+='<div class="card"><div class="card-title">Runtime Protection (ARP)</div>';
|
|
614
|
+
h+=dr('ARP Status',rt.arpActive?'<span class="status-active">Active</span>':'<span class="status-inactive">Inactive</span>');
|
|
615
|
+
h+=dr('Processes Spawned',rt.processesSpawned)+dr('Network Connections',rt.networkConnections);
|
|
616
|
+
h+=dr('Anomalies','<span style="color:'+(rt.anomalies>0?'var(--amber)':'var(--green)')+'">'+rt.anomalies+'</span>')+'</div>';
|
|
617
|
+
h+='<div class="card"><div class="card-title">Credential Exposure</div>';
|
|
618
|
+
h+=dr('Access Attempts',cred.accessAttempts)+dr('Unique Credentials',cred.uniqueCredentials);
|
|
619
|
+
var providers=Object.keys(cred.byProvider||{});
|
|
620
|
+
if(providers.length>0){h+='<div class="detail-row"><span class="detail-key">By Provider</span><span class="detail-val"> </span></div><div class="provider-list">';for(var p=0;p<providers.length;p++)h+='<span class="provider-tag">'+esc(providers[p])+': '+cred.byProvider[providers[p]]+'</span>';h+='</div>';}
|
|
621
|
+
h+='</div>';
|
|
622
|
+
var sc=report.supplyChain;
|
|
623
|
+
h+='<div class="card"><div class="card-title">Supply Chain</div>';
|
|
624
|
+
h+=dr('Packages Installed',sc.packagesInstalled)+dr('Advisories Found','<span style="color:'+(sc.advisoriesFound>0?'var(--amber)':'var(--green)')+'">'+sc.advisoriesFound+'</span>');
|
|
625
|
+
h+=dr('Blocked Installs','<span style="color:'+(sc.blockedInstalls>0?'var(--red)':'var(--text)')+'">'+sc.blockedInstalls+'</span>')+'</div>';
|
|
626
|
+
h+='<div class="card"><div class="card-title">Config Integrity</div>';
|
|
627
|
+
h+=dr('Files Monitored',ci.filesMonitored);
|
|
628
|
+
h+=dr('Signature Status',ci.signatureStatus==='signed'||ci.signatureStatus==='valid'?'<span class="status-active">Valid</span>':ci.signatureStatus==='unsigned'?'<span style="color:var(--amber)">Unsigned</span>':'<span class="status-inactive">'+esc(ci.signatureStatus)+'</span>');
|
|
629
|
+
if(ci.tamperedFiles&&ci.tamperedFiles.length>0){h+=dr('Tampered Files','<span class="status-inactive">'+ci.tamperedFiles.length+'</span>');for(var t=0;t<ci.tamperedFiles.length;t++)h+='<div style="color:var(--red);font-size:12px;padding:2px 0">'+esc(ci.tamperedFiles[t])+'</div>';}
|
|
630
|
+
else if(ci.filesMonitored>0) h+=dr('Integrity','<span class="status-active">All files valid</span>');
|
|
631
|
+
h+='</div></div>';return h;
|
|
632
|
+
}
|
|
633
|
+
function dr(k,v){return '<div class="detail-row"><span class="detail-key">'+esc(k)+'</span><span class="detail-val">'+v+'</span></div>';}
|
|
634
|
+
|
|
635
|
+
// ======================== TIMELINE ========================
|
|
636
|
+
function renderTimeline() {
|
|
637
|
+
if(!narrative) {
|
|
638
|
+
var agents=report.agentActivity.byAgent;var akeys=Object.keys(agents);
|
|
639
|
+
if(akeys.length>0){
|
|
640
|
+
var h='<h2 class="section-title">Recent Agent Activity</h2>';
|
|
641
|
+
h+='<div class="card"><table class="data-table"><thead><tr><th>Agent</th><th>Sessions</th><th>Actions</th><th>First Seen</th><th>Last Seen</th></tr></thead><tbody>';
|
|
642
|
+
for(var i=0;i<akeys.length;i++){var name=akeys[i],a=agents[name];
|
|
643
|
+
h+='<tr><td style="font-weight:600;color:var(--primary)">'+esc(name)+'</td><td>'+a.sessions+'</td><td>'+a.actions+'</td><td style="font-size:11px;color:var(--dim)">'+esc(formatTs(a.firstSeen))+'</td><td style="font-size:11px;color:var(--dim)">'+esc(formatTs(a.lastSeen))+'</td></tr>';}
|
|
644
|
+
h+='</tbody></table></div>';
|
|
645
|
+
h+='<div class="card" style="margin-top:16px"><div class="empty-state">For AI-powered event analysis and narrative, run with the --analyze flag.</div></div>';
|
|
646
|
+
return h;
|
|
647
|
+
}
|
|
648
|
+
return '<h2 class="section-title">Event Timeline</h2><div class="card"><div class="empty-state">No agent activity recorded. Use --analyze flag to generate AI-powered event analysis after events are logged.</div></div>';
|
|
649
|
+
}
|
|
650
|
+
var h='<h2 class="section-title">Event Timeline</h2><div class="card">';
|
|
651
|
+
if(narrative.summary){h+='<div class="narrative-block"><h4>Summary</h4><div class="narrative-text">'+esc(narrative.summary)+'</div></div>';}
|
|
652
|
+
if(narrative.highlights&&narrative.highlights.length>0){h+='<div class="narrative-block"><h4>Highlights</h4><ul class="narrative-list narrative-highlight">';for(var i=0;i<narrative.highlights.length;i++)h+='<li>'+esc(narrative.highlights[i])+'</li>';h+='</ul></div>';}
|
|
653
|
+
if(narrative.concerns&&narrative.concerns.length>0){h+='<div class="narrative-block"><h4>Concerns</h4><ul class="narrative-list narrative-concern">';for(var i=0;i<narrative.concerns.length;i++)h+='<li>'+esc(narrative.concerns[i])+'</li>';h+='</ul></div>';}
|
|
654
|
+
if(narrative.recommendations&&narrative.recommendations.length>0){h+='<div class="narrative-block"><h4>Recommendations</h4><ul class="narrative-list narrative-rec">';for(var i=0;i<narrative.recommendations.length;i++)h+='<li>'+esc(narrative.recommendations[i])+'</li>';h+='</ul></div>';}
|
|
655
|
+
h+='</div>';return h;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// --- Cross-tab navigation ---
|
|
659
|
+
window.navigateToFinding=function(id){
|
|
660
|
+
var tabs=document.querySelectorAll('.nav-tab');
|
|
661
|
+
for(var i=0;i<tabs.length;i++) tabs[i].classList.toggle('active',tabs[i].dataset.page==='findings');
|
|
662
|
+
var pages=document.querySelectorAll('.page');
|
|
663
|
+
for(var i=0;i<pages.length;i++) pages[i].classList.toggle('active',pages[i].id==='page-findings');
|
|
664
|
+
currentPage='findings';renderPage('findings');
|
|
665
|
+
var search=document.getElementById('findings-search');
|
|
666
|
+
if(search){search.value='';window._filterFindings('');}
|
|
667
|
+
setTimeout(function(){
|
|
668
|
+
var rows=document.querySelectorAll('#findings-table .finding-row');
|
|
669
|
+
for(var ri=0;ri<rows.length;ri++){
|
|
670
|
+
var idCell=rows[ri].querySelector('.finding-id');
|
|
671
|
+
if(idCell&&idCell.textContent===id){
|
|
672
|
+
var det=document.getElementById('finding-detail-'+rows[ri].dataset.idx);
|
|
673
|
+
if(det&&!det.classList.contains('open')) det.classList.add('open');
|
|
674
|
+
rows[ri].scrollIntoView({behavior:'smooth',block:'center'});
|
|
675
|
+
rows[ri].classList.add('finding-highlight');
|
|
676
|
+
setTimeout(function(r){r.classList.remove('finding-highlight');}.bind(null,rows[ri]),1600);
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
},50);
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
// --- Copy command ---
|
|
684
|
+
window.copyCmd=function(btn){var cmd=btn.getAttribute('data-cmd');if(!cmd)return;if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(cmd).then(function(){btn.textContent='OK';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},1500);});}else{var ta=document.createElement('textarea');ta.value=cmd;ta.style.position='fixed';ta.style.left='-9999px';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);btn.textContent='OK';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},1500);}};
|
|
685
|
+
|
|
686
|
+
init();
|
|
687
|
+
})();
|
|
688
|
+
`;
|
|
689
|
+
//# sourceMappingURL=report-html.js.map
|