@jishankai/solid-cli 1.0.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/LICENSE +21 -0
- package/README.md +276 -0
- package/config/default.json +79 -0
- package/package.json +60 -0
- package/src/Orchestrator.js +482 -0
- package/src/agents/BaseAgent.js +35 -0
- package/src/agents/BlockchainAgent.js +453 -0
- package/src/agents/DeFiSecurityAgent.js +257 -0
- package/src/agents/NetworkAgent.js +341 -0
- package/src/agents/PermissionAgent.js +192 -0
- package/src/agents/PersistenceAgent.js +361 -0
- package/src/agents/ProcessAgent.js +572 -0
- package/src/agents/ResourceAgent.js +217 -0
- package/src/agents/SystemAgent.js +173 -0
- package/src/config/ConfigManager.js +446 -0
- package/src/index.js +629 -0
- package/src/llm/LLMAnalyzer.js +705 -0
- package/src/logging/Logger.js +352 -0
- package/src/report/ReportManager.js +445 -0
- package/src/report/generators/MarkdownGenerator.js +173 -0
- package/src/report/generators/PDFGenerator.js +616 -0
- package/src/report/templates/report.hbs +465 -0
- package/src/report/utils/formatter.js +426 -0
- package/src/report/utils/sanitizer.js +275 -0
- package/src/utils/commander.js +42 -0
- package/src/utils/signature.js +121 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
{{!-- Unified Report Template for both PDF (HTML) and Markdown --}}
|
|
2
|
+
<div class="header">
|
|
3
|
+
{{#ifEquals format 'markdown'}}
|
|
4
|
+
# MacOS Security Analysis Report
|
|
5
|
+
|
|
6
|
+
**Generated:** {{formatDate metadata.generatedAt}} at {{formatTime metadata.generatedAt}}
|
|
7
|
+
**Hostname:** {{system.hostname}}
|
|
8
|
+
**macOS Version:** {{system.osVersion}}
|
|
9
|
+
**Analysis Mode:** {{uppercase system.mode}}
|
|
10
|
+
**Overall Risk Level:** {{markdownRiskBadge summary.overallRisk}}
|
|
11
|
+
**Report ID:** {{metadata.reportId}}
|
|
12
|
+
{{else}}
|
|
13
|
+
<h1>MacOS Security Analysis Report</h1>
|
|
14
|
+
<div class="subtitle">
|
|
15
|
+
Generated: {{formatDate metadata.generatedAt}} at {{formatTime metadata.generatedAt}} |
|
|
16
|
+
Hostname: {{system.hostname}} |
|
|
17
|
+
macOS Version: {{system.osVersion}} |
|
|
18
|
+
Analysis Mode: {{uppercase system.mode}} |
|
|
19
|
+
Overall Risk: <span class="risk-badge risk-{{summary.overallRisk}}">{{uppercase summary.overallRisk}}</span>
|
|
20
|
+
</div>
|
|
21
|
+
{{/ifEquals}}
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
{{!-- Executive Summary --}}
|
|
25
|
+
<div class="executive-summary">
|
|
26
|
+
{{#ifEquals format 'markdown'}}
|
|
27
|
+
## Executive Summary
|
|
28
|
+
|
|
29
|
+
This report contains the results of a comprehensive {{system.mode}} analysis of the system.
|
|
30
|
+
|
|
31
|
+
### Key Metrics
|
|
32
|
+
|
|
33
|
+
{{#ifEquals format 'markdown'}}
|
|
34
|
+
| Metric | Value |
|
|
35
|
+
|--------|-------|
|
|
36
|
+
| Total Findings | {{summary.totalFindings}} |
|
|
37
|
+
| High Risk | {{summary.highRiskFindings}} 🔴 |
|
|
38
|
+
| Medium Risk | {{summary.mediumRiskFindings}} 🟡 |
|
|
39
|
+
| Low Risk | {{summary.lowRiskFindings}} 🟢 |
|
|
40
|
+
| Risk Score | {{calculateRiskScore summary}}/100 |
|
|
41
|
+
{{else}}
|
|
42
|
+
<h2>Executive Summary</h2>
|
|
43
|
+
<p>This report contains the results of a comprehensive {{system.mode}} analysis of the system.</p>
|
|
44
|
+
|
|
45
|
+
<h3>Key Metrics</h3>
|
|
46
|
+
<div class="metrics-grid">
|
|
47
|
+
<div class="metric-card">
|
|
48
|
+
<div class="metric-value">{{summary.totalFindings}}</div>
|
|
49
|
+
<div class="metric-label">Total Findings</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="metric-card">
|
|
52
|
+
<div class="metric-value" style="color: #dc2626;">{{summary.highRiskFindings}}</div>
|
|
53
|
+
<div class="metric-label">High Risk</div>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="metric-card">
|
|
56
|
+
<div class="metric-value" style="color: #ca8a04;">{{summary.mediumRiskFindings}}</div>
|
|
57
|
+
<div class="metric-label">Medium Risk</div>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="metric-card">
|
|
60
|
+
<div class="metric-value" style="color: #16a34a;">{{summary.lowRiskFindings}}</div>
|
|
61
|
+
<div class="metric-label">Low Risk</div>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="metric-card">
|
|
64
|
+
<div class="metric-value">{{calculateRiskScore summary}}/100</div>
|
|
65
|
+
<div class="metric-label">Risk Score</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
{{/ifEquals}}
|
|
69
|
+
|
|
70
|
+
### Analysis Phases
|
|
71
|
+
|
|
72
|
+
{{#ifEquals format 'markdown'}}
|
|
73
|
+
{{#each analysisPhases}}
|
|
74
|
+
#### {{phase}}
|
|
75
|
+
- Status: {{#if completed}}✅ Completed{{else}}⏳ In Progress{{/if}}
|
|
76
|
+
{{#each extensions}}
|
|
77
|
+
- Extension: {{this}}
|
|
78
|
+
{{/each}}
|
|
79
|
+
{{/each}}
|
|
80
|
+
{{else}}
|
|
81
|
+
<div class="section">
|
|
82
|
+
<h2>Analysis Phases</h2>
|
|
83
|
+
{{#each analysisPhases}}
|
|
84
|
+
<div style="margin-bottom: 1rem;">
|
|
85
|
+
<strong>{{phase}}</strong> - {{#if completed}}✅ Completed{{else}}⏳ In Progress{{/if}}
|
|
86
|
+
{{#each extensions}}
|
|
87
|
+
<div style="margin-left: 1rem; color: #64748b;">Extension: {{this}}</div>
|
|
88
|
+
{{/each}}
|
|
89
|
+
</div>
|
|
90
|
+
{{/each}}
|
|
91
|
+
</div>
|
|
92
|
+
{{/ifEquals}}
|
|
93
|
+
|
|
94
|
+
{{else}}
|
|
95
|
+
{{/ifEquals}}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{{!-- Risk Distribution Chart --}}
|
|
99
|
+
{{#ifEquals format 'markdown'}}
|
|
100
|
+
## Risk Distribution
|
|
101
|
+
|
|
102
|
+
| Risk Level | Count | Percentage |
|
|
103
|
+
|------------|-------|------------|
|
|
104
|
+
| 🔴 High | {{visualizations.riskDistribution.high}} | {{visualizations.riskDistribution.highPercentage}}% |
|
|
105
|
+
| 🟡 Medium | {{visualizations.riskDistribution.medium}} | {{visualizations.riskDistribution.mediumPercentage}}% |
|
|
106
|
+
| 🟢 Low | {{visualizations.riskDistribution.low}} | {{visualizations.riskDistribution.lowPercentage}}% |
|
|
107
|
+
|
|
108
|
+
{{else}}
|
|
109
|
+
<div class="section">
|
|
110
|
+
<h2>Risk Distribution</h2>
|
|
111
|
+
<div class="risk-distribution">
|
|
112
|
+
<div class="risk-segment">
|
|
113
|
+
<div class="risk-count" style="color: {{riskColor 'high'}};">{{visualizations.riskDistribution.high}}</div>
|
|
114
|
+
<div class="risk-percentage">{{visualizations.riskDistribution.highPercentage}}%</div>
|
|
115
|
+
<div class="risk-label">High</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div class="risk-segment">
|
|
119
|
+
<div class="risk-count" style="color: {{riskColor 'medium'}};">{{visualizations.riskDistribution.medium}}</div>
|
|
120
|
+
<div class="risk-percentage">{{visualizations.riskDistribution.mediumPercentage}}%</div>
|
|
121
|
+
<div class="risk-label">Medium</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div class="risk-segment">
|
|
125
|
+
<div class="risk-count" style="color: {{riskColor 'low'}};">{{visualizations.riskDistribution.low}}</div>
|
|
126
|
+
<div class="risk-percentage">{{visualizations.riskDistribution.lowPercentage}}%</div>
|
|
127
|
+
<div class="risk-label">Low</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
{{/ifEquals}}
|
|
132
|
+
|
|
133
|
+
{{!-- Detailed Findings --}}
|
|
134
|
+
{{#ifEquals format 'markdown'}}
|
|
135
|
+
## Detailed Findings
|
|
136
|
+
|
|
137
|
+
{{#each findings}}
|
|
138
|
+
### {{name}}
|
|
139
|
+
|
|
140
|
+
**Risk Level:** {{markdownRiskBadge overallRisk}}
|
|
141
|
+
**Total Findings:** {{summary.total}}
|
|
142
|
+
|
|
143
|
+
{{#ifEquals summary.high 0}}
|
|
144
|
+
{{else}}
|
|
145
|
+
#### 🔴 High Risk Findings ({{summary.high}})
|
|
146
|
+
|
|
147
|
+
{{#each findings}}
|
|
148
|
+
{{#ifEquals risk 'high'}}
|
|
149
|
+
##### {{type}}
|
|
150
|
+
|
|
151
|
+
{{description}}
|
|
152
|
+
|
|
153
|
+
{{#if command}}**Command:** `{{command}}`{{/if}}
|
|
154
|
+
{{#if path}}**Path:** `{{path}}`{{/if}}
|
|
155
|
+
{{#if program}}**Program:** `{{program}}`{{/if}}
|
|
156
|
+
{{#if pid}}**PID:** {{pid}}{{/if}}
|
|
157
|
+
|
|
158
|
+
{{#if risks}}
|
|
159
|
+
**Risk Factors:**
|
|
160
|
+
{{#each risks}}
|
|
161
|
+
- {{this}}
|
|
162
|
+
{{/each}}
|
|
163
|
+
{{/if}}
|
|
164
|
+
|
|
165
|
+
{{/ifEquals}}
|
|
166
|
+
{{/each}}
|
|
167
|
+
{{/ifEquals}}
|
|
168
|
+
|
|
169
|
+
{{#ifEquals summary.medium 0}}
|
|
170
|
+
{{else}}
|
|
171
|
+
#### 🟡 Medium Risk Findings ({{summary.medium}})
|
|
172
|
+
|
|
173
|
+
{{#each findings}}
|
|
174
|
+
{{#ifEquals risk 'medium'}}
|
|
175
|
+
##### {{type}}
|
|
176
|
+
|
|
177
|
+
{{description}}
|
|
178
|
+
|
|
179
|
+
{{#if command}}**Command:** `{{command}}`{{/if}}
|
|
180
|
+
{{#if path}}**Path:** `{{path}}`{{/if}}
|
|
181
|
+
{{#if program}}**Program:** `{{program}}`{{/if}}
|
|
182
|
+
{{#if pid}}**PID:** {{pid}}{{/if}}
|
|
183
|
+
|
|
184
|
+
{{#if risks}}
|
|
185
|
+
**Risk Factors:**
|
|
186
|
+
{{#each risks}}
|
|
187
|
+
- {{this}}
|
|
188
|
+
{{/each}}
|
|
189
|
+
{{/if}}
|
|
190
|
+
|
|
191
|
+
{{/ifEquals}}
|
|
192
|
+
{{/each}}
|
|
193
|
+
{{/ifEquals}}
|
|
194
|
+
|
|
195
|
+
{{#ifEquals summary.low 0}}
|
|
196
|
+
{{else}}
|
|
197
|
+
#### 🟢 Low Risk Findings ({{summary.low}})
|
|
198
|
+
|
|
199
|
+
{{#each findings}}
|
|
200
|
+
{{#ifEquals risk 'low'}}
|
|
201
|
+
##### {{type}}
|
|
202
|
+
|
|
203
|
+
{{description}}
|
|
204
|
+
|
|
205
|
+
{{#if command}}**Command:** `{{command}}`{{/if}}
|
|
206
|
+
{{#if path}}**Path:** `{{path}}`{{/if}}
|
|
207
|
+
{{#if program}}**Program:** `{{program}}`{{/if}}
|
|
208
|
+
{{#if pid}}**PID:** {{pid}}{{/if}}
|
|
209
|
+
|
|
210
|
+
{{#if risks}}
|
|
211
|
+
**Risk Factors:**
|
|
212
|
+
{{#each risks}}
|
|
213
|
+
- {{this}}
|
|
214
|
+
{{/each}}
|
|
215
|
+
{{/if}}
|
|
216
|
+
|
|
217
|
+
{{/ifEquals}}
|
|
218
|
+
{{/each}}
|
|
219
|
+
{{/ifEquals}}
|
|
220
|
+
|
|
221
|
+
{{#if error}}
|
|
222
|
+
❌ **Error:** {{error}}
|
|
223
|
+
|
|
224
|
+
{{else}}
|
|
225
|
+
✅ No significant findings.
|
|
226
|
+
|
|
227
|
+
{{/if}}
|
|
228
|
+
|
|
229
|
+
{{/each}}
|
|
230
|
+
|
|
231
|
+
{{else}}
|
|
232
|
+
<div class="section">
|
|
233
|
+
<h2>Detailed Findings</h2>
|
|
234
|
+
{{#each findings}}
|
|
235
|
+
<div class="agent-section">
|
|
236
|
+
<h3>{{name}}</h3>
|
|
237
|
+
<div class="finding-header">
|
|
238
|
+
<span class="finding-title">{{name}} Agent</span>
|
|
239
|
+
<span class="finding-risk risk-{{overallRisk}}">{{riskBadge overallRisk}}</span>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<div class="finding-summary">
|
|
243
|
+
<strong>Total Findings:</strong> {{summary.total}}
|
|
244
|
+
({{summary.high}} High, {{summary.medium}} Medium, {{summary.low}} Low)
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
{{#if error}}
|
|
248
|
+
<div class="finding-error">❌ Error: {{error}}</div>
|
|
249
|
+
{{else}}
|
|
250
|
+
|
|
251
|
+
{{#ifGt summary.total 0}}
|
|
252
|
+
{{#each findings}}
|
|
253
|
+
<div class="finding">
|
|
254
|
+
<div class="finding-header">
|
|
255
|
+
<span class="finding-title">{{type}}</span>
|
|
256
|
+
<span class="finding-risk risk-{{risk}}">{{riskBadge risk}}</span>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
<div class="finding-description">{{description}}</div>
|
|
260
|
+
|
|
261
|
+
{{#if (hasAny command path program pid)}}
|
|
262
|
+
<div class="finding-details">
|
|
263
|
+
{{#if command}}
|
|
264
|
+
<div class="detail-row">
|
|
265
|
+
<span class="detail-label">Command:</span> <code>{{command}}</code>
|
|
266
|
+
</div>
|
|
267
|
+
{{/if}}
|
|
268
|
+
|
|
269
|
+
{{#if path}}
|
|
270
|
+
<div class="detail-row">
|
|
271
|
+
<span class="detail-label">Path:</span> <code>{{path}}</code>
|
|
272
|
+
</div>
|
|
273
|
+
{{/if}}
|
|
274
|
+
|
|
275
|
+
{{#if program}}
|
|
276
|
+
<div class="detail-row">
|
|
277
|
+
<span class="detail-label">Program:</span> <code>{{program}}</code>
|
|
278
|
+
</div>
|
|
279
|
+
{{/if}}
|
|
280
|
+
|
|
281
|
+
{{#if pid}}
|
|
282
|
+
<div class="detail-row">
|
|
283
|
+
<span class="detail-label">PID:</span> {{pid}}
|
|
284
|
+
</div>
|
|
285
|
+
{{/if}}
|
|
286
|
+
</div>
|
|
287
|
+
{{/if}}
|
|
288
|
+
|
|
289
|
+
{{#if risks}}
|
|
290
|
+
<div class="finding-details">
|
|
291
|
+
<div class="detail-label">Risk Factors:</div>
|
|
292
|
+
<ul style="margin: 0.25rem 0; padding-left: 0.85rem;">
|
|
293
|
+
{{#each risks}}
|
|
294
|
+
<li style="margin: 0.1rem 0;">{{this}}</li>
|
|
295
|
+
{{/each}}
|
|
296
|
+
</ul>
|
|
297
|
+
</div>
|
|
298
|
+
{{/if}}
|
|
299
|
+
</div>
|
|
300
|
+
{{/each}}
|
|
301
|
+
{{else}}
|
|
302
|
+
<div class="finding">✅ No significant findings.</div>
|
|
303
|
+
{{/ifGt}}
|
|
304
|
+
|
|
305
|
+
{{/if}}
|
|
306
|
+
</div>
|
|
307
|
+
{{/each}}
|
|
308
|
+
</div>
|
|
309
|
+
{{/ifEquals}}
|
|
310
|
+
|
|
311
|
+
{{!-- Sensitive Data Alerts (always visible if detected) --}}
|
|
312
|
+
{{#if (or sensitivePatterns sensitiveDataAlerts)}}
|
|
313
|
+
{{#ifEquals format 'markdown'}}
|
|
314
|
+
## Sensitive Data Alerts
|
|
315
|
+
|
|
316
|
+
{{#if sensitivePatterns.length}}
|
|
317
|
+
**Detected Patterns:**
|
|
318
|
+
{{#each sensitivePatterns}}
|
|
319
|
+
- **{{name}}** — {{count}} occurrence(s){{#if samples}}; samples: {{#each samples}}`{{this}}` {{/each}}{{/if}}
|
|
320
|
+
{{/each}}
|
|
321
|
+
{{/if}}
|
|
322
|
+
|
|
323
|
+
{{#if sensitiveDataAlerts.length}}
|
|
324
|
+
**Locations:**
|
|
325
|
+
{{#each sensitiveDataAlerts}}
|
|
326
|
+
- **{{pattern}}** — Agent: {{agent}}, Field: {{field}}, Path/Context: `{{path}}` {{#if samples}}(samples: {{#each samples}}`{{this}}` {{/each}}){{/if}}
|
|
327
|
+
{{/each}}
|
|
328
|
+
{{else}}
|
|
329
|
+
- (No specific locations captured)
|
|
330
|
+
{{/if}}
|
|
331
|
+
|
|
332
|
+
> AI analysis was blocked to avoid sending the above data. Remove/redact and rerun, or continue with report-only mode.
|
|
333
|
+
{{else}}
|
|
334
|
+
<div class="section">
|
|
335
|
+
<h2>Sensitive Data Alerts</h2>
|
|
336
|
+
<div style="background: #fff7ed; border: 1px solid #f97316; border-radius: 8px; padding: 1.5rem; margin-bottom: 1rem;">
|
|
337
|
+
{{#if sensitivePatterns.length}}
|
|
338
|
+
<div style="margin-bottom: 0.75rem;"><strong>Detected Patterns:</strong></div>
|
|
339
|
+
<ul style="margin: 0 0 1rem 1rem;">
|
|
340
|
+
{{#each sensitivePatterns}}
|
|
341
|
+
<li><strong>{{name}}</strong> — {{count}} occurrence(s){{#if samples}}; samples: {{#each samples}}<code>{{this}}</code> {{/each}}{{/if}}</li>
|
|
342
|
+
{{/each}}
|
|
343
|
+
</ul>
|
|
344
|
+
{{/if}}
|
|
345
|
+
|
|
346
|
+
<div style="margin-bottom: 0.5rem;"><strong>Locations:</strong></div>
|
|
347
|
+
{{#if sensitiveDataAlerts.length}}
|
|
348
|
+
<ul style="margin: 0; padding-left: 1rem;">
|
|
349
|
+
{{#each sensitiveDataAlerts}}
|
|
350
|
+
<li><strong>{{pattern}}</strong> — Agent: {{agent}}, Field: {{field}}, Path/Context: <code>{{path}}</code>{{#if samples}} (samples: {{#each samples}}<code>{{this}}</code> {{/each}}){{/if}}</li>
|
|
351
|
+
{{/each}}
|
|
352
|
+
</ul>
|
|
353
|
+
{{else}}
|
|
354
|
+
<p style="margin: 0;">(No specific locations captured)</p>
|
|
355
|
+
{{/if}}
|
|
356
|
+
|
|
357
|
+
<p style="margin-top: 0.75rem; color: #475569;">AI analysis was blocked to avoid sending the above data. Remove/redact and rerun, or continue with report-only mode.</p>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
{{/ifEquals}}
|
|
361
|
+
{{/if}}
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
{{!-- Recommendations --}}
|
|
365
|
+
{{#if recommendations}}
|
|
366
|
+
{{#ifEquals format 'markdown'}}
|
|
367
|
+
## Recommendations
|
|
368
|
+
|
|
369
|
+
{{#each recommendations}}
|
|
370
|
+
### {{title}} ({{uppercase priority}} Priority)
|
|
371
|
+
|
|
372
|
+
{{description}}
|
|
373
|
+
|
|
374
|
+
**Actions:**
|
|
375
|
+
{{#each actions}}
|
|
376
|
+
- {{this}}
|
|
377
|
+
{{/each}}
|
|
378
|
+
|
|
379
|
+
{{/each}}
|
|
380
|
+
|
|
381
|
+
{{else}}
|
|
382
|
+
<div class="recommendations">
|
|
383
|
+
<h3>Recommendations</h3>
|
|
384
|
+
{{#each recommendations}}
|
|
385
|
+
<div class="recommendation">
|
|
386
|
+
<h4>{{title}} ({{uppercase priority}} Priority)</h4>
|
|
387
|
+
<p>{{description}}</p>
|
|
388
|
+
<ul class="recommendation-actions">
|
|
389
|
+
{{#each actions}}
|
|
390
|
+
<li>{{this}}</li>
|
|
391
|
+
{{/each}}
|
|
392
|
+
</ul>
|
|
393
|
+
</div>
|
|
394
|
+
{{/each}}
|
|
395
|
+
</div>
|
|
396
|
+
{{/ifEquals}}
|
|
397
|
+
{{/if}}
|
|
398
|
+
|
|
399
|
+
{{!-- Compliance Information --}}
|
|
400
|
+
{{#ifEquals format 'markdown'}}
|
|
401
|
+
## Compliance Framework Mapping
|
|
402
|
+
|
|
403
|
+
{{#each compliance}}
|
|
404
|
+
### {{@key}}
|
|
405
|
+
{{#if this.length}}
|
|
406
|
+
- {{#each this}}- {{this}}{{/each}}
|
|
407
|
+
{{else}}
|
|
408
|
+
- No findings mapped to this framework
|
|
409
|
+
{{/if}}
|
|
410
|
+
|
|
411
|
+
{{/each}}
|
|
412
|
+
|
|
413
|
+
{{else}}
|
|
414
|
+
{{#if compliance}}
|
|
415
|
+
<div class="section">
|
|
416
|
+
<h2>Compliance Framework Mapping</h2>
|
|
417
|
+
{{#each compliance}}
|
|
418
|
+
<div style="margin-bottom: 1rem;">
|
|
419
|
+
<strong>{{@key}}:</strong>
|
|
420
|
+
{{#if this.length}}
|
|
421
|
+
<ul style="margin: 0.5rem 0; padding-left: 1rem;">
|
|
422
|
+
{{#each this}}
|
|
423
|
+
<li>{{this}}</li>
|
|
424
|
+
{{/each}}
|
|
425
|
+
</ul>
|
|
426
|
+
{{else}}
|
|
427
|
+
<span style="color: #16a34a;">✅ No findings mapped to this framework</span>
|
|
428
|
+
{{/if}}
|
|
429
|
+
</div>
|
|
430
|
+
{{/each}}
|
|
431
|
+
</div>
|
|
432
|
+
{{/if}}
|
|
433
|
+
{{/ifEquals}}
|
|
434
|
+
|
|
435
|
+
{{!-- Appendix --}}
|
|
436
|
+
{{#ifEquals format 'markdown'}}
|
|
437
|
+
## Appendix: Raw Data
|
|
438
|
+
|
|
439
|
+
```json
|
|
440
|
+
{{json results}}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
{{else}}
|
|
444
|
+
<div class="section">
|
|
445
|
+
<h2>Appendix: Raw Data</h2>
|
|
446
|
+
<pre style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 1rem; font-size: 9px; overflow-x: auto;">{{json results}}</pre>
|
|
447
|
+
</div>
|
|
448
|
+
{{/ifEquals}}
|
|
449
|
+
|
|
450
|
+
{{#ifEquals format 'markdown'}}
|
|
451
|
+
---
|
|
452
|
+
<div class="footer">
|
|
453
|
+
**Report Generated:** {{formatDate metadata.generatedAt}} at {{formatTime metadata.generatedAt}}
|
|
454
|
+
**Report ID:** {{metadata.reportId}}
|
|
455
|
+
**Version:** {{metadata.version}}
|
|
456
|
+
</div>
|
|
457
|
+
{{else}}
|
|
458
|
+
<div class="footer">
|
|
459
|
+
<p>
|
|
460
|
+
<strong>Report Generated:</strong> {{formatDate metadata.generatedAt}} at {{formatTime metadata.generatedAt}} |
|
|
461
|
+
<strong>Report ID:</strong> {{metadata.reportId}} |
|
|
462
|
+
<strong>Version:</strong> {{metadata.version}}
|
|
463
|
+
</p>
|
|
464
|
+
</div>
|
|
465
|
+
{{/ifEquals}}
|