fix: ingestion pipeline, add parsers/samples, scan cancel, design cleanup

- Fix Finding changeset: add scan_id/vulnerability_id/asset_id to cast
  whitelist (was silently dropping foreign keys, blocking all ingestion)
- Add SPDX dispatch as no-op (BOM format, no vulnerabilities)
- Add Grype parser for Anchore container scan JSON output
- Add Snyk parser for npm/pip test JSON output
- Add scan cancellation (pending/processing scans only)
- Add cancelled status to Scan schema and scan_status component
- Fix stat card accent colors (use border-l-* directional utilities)
- Replace design/ folder with DESIGN.md reference doc
- Expand all sample files to production-grade size (8 tools, 82 findings)
- Update Detector for Grype and Snyk format detection
- Update CLAUDE.md with new parser inventory
This commit is contained in:
Christopher Fahlin
2026-05-13 20:50:46 -07:00
parent 79a0e749f8
commit de7a3c6c2c
25 changed files with 1506 additions and 706 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ Phoenix 1.8 app with LiveView 1.1, PostgreSQL (Ecto), Bandit HTTP server.
### Bounded Contexts
- **`SecDashboard.Security`** — domain context. Schemas: `Scan`, `Asset`, `Vulnerability`, `Finding`. CRUD with Flop-powered queries. Assets and Vulnerabilities use upsert (conflict on unique keys). Triage via `update_finding_status/2` with PubSub broadcast.
- **`SecDashboard.Ingestion`** — pipeline context. `ParseJob` (Oban worker), `Detector` (format/tool identification), and `Parsers` (Trivy, SARIF, CycloneDX) that normalize diverse tool outputs into the Security domain.
- **`SecDashboard.Ingestion`** — pipeline context. `ParseJob` (Oban worker), `Detector` (format/tool identification), and `Parsers` (Trivy, SARIF, CycloneDX, Grype, Snyk) that normalize diverse tool outputs into the Security domain. SPDX BOMs are detected but produce zero findings (BOM-only format). Scans can be cancelled while pending/processing.
### Ingestion Pipeline
+123
View File
@@ -0,0 +1,123 @@
# DESIGN.md
Design system reference for Bulwark, a Security Operations Center (SOC) dashboard.
## Aesthetic Profile: Auditor Professional
A high-density, dark-mode-only interface inspired by GitHub Settings, Vercel Dashboard, and Linear. No shadows, gradients, or decorative elements. Focus on borders, grid lines, and clear visual hierarchy.
## Color Palette
### Base
Monochromatic zinc scale. All surfaces use dark palette directly (no `dark:` variants).
| Role | Token | Usage |
|--------------|-------------|------------------------------------------|
| Background | `zinc-950` | Body, sidebar, header, status bar |
| Surface | `zinc-900` | Cards, panels, active nav items |
| Border | `zinc-800` | All dividers, card borders, table lines |
| Muted text | `zinc-500` | Labels, secondary text, inactive nav |
| Body text | `zinc-300` | Standard content text |
| Emphasis | `zinc-100` | Headings, active items, primary text |
### Semantic Accents
Color is strictly reserved for severity and status indicators. Never use for decoration.
| Severity | Color | Badge Label | Usage |
|------------|----------------|-------------|------------------------------|
| Critical | `red-500` | CRT | Border, bg, text at 10%/20% |
| High | `orange-500` | HI | Border, bg, text at 10%/20% |
| Medium | `yellow-500` | MED | Border, bg, text at 10%/20% |
| Low | `green-500` | LOW | Border, bg, text at 10%/20% |
| Info | `zinc-500` | INF | Border, bg, text at 10%/20% |
### Status Colors
| Status | Color | Usage |
|------------|----------------|----------------------------------|
| Connected | `green-500` | Status bar dot + text |
| Processing | `sky-500` | Scan badge with spinner |
| Completed | `green-500` | Scan badge |
| Failed | `red-500` | Scan badge |
| Cancelled | `zinc-500` | Scan badge with line-through |
## Typography
| Role | Font | Weight | Size |
|-----------------|-----------------|----------|-------------|
| UI text | Inter | 400-600 | text-sm |
| Technical data | JetBrains Mono | 400-500 | text-xs |
| Micro labels | JetBrains Mono | 500 | text-xxs |
| Headings | Inter | 600-700 | text-lg+ |
Custom size `text-xxs` = `0.625rem / 0.875rem` defined via `@theme` in `app.css`.
Fonts loaded via Google Fonts `<link>` in `root.html.heex`.
## Density
- Row padding: `py-1.5` (table cells), `py-2` (nav items)
- Card padding: `p-5`
- Max border radius: `rounded-md`
- Borders: `border-zinc-800` (1px solid)
- No shadows anywhere
## Layout
### App Shell
- **Sidebar**: 52px collapsed / 180px expanded (desktop). Full-width overlay on mobile. Controlled by `data-expanded` attribute + CSS, decoupled from LiveView DOM patching.
- **Header**: 48px (`h-12`). Breadcrumbs left, system integrity pulse right.
- **Main**: `flex-1 overflow-y-auto`. Dot-grid background pattern (`radial-gradient`).
- **Status Bar**: 28px (`h-7`). Connection status, sync timer, pipeline health, uptime counter.
### Navigation
| Section | Icon | Path |
|-----------------|----------------------------|---------------------|
| Dashboard | `hero-chart-bar-square` | `/` |
| Scans | `hero-arrow-up-tray` | `/scans` |
| Vulnerabilities | `hero-shield-exclamation` | `/vulnerabilities` |
| Findings | `hero-magnifying-glass-circle` | `/findings` |
| Assets | `hero-server-stack` | `/assets` |
Active state: `bg-zinc-900 text-zinc-100`. Inactive: `text-zinc-500 hover:text-zinc-300 hover:bg-zinc-900/50`.
## Components
### Stat Card
Dark card with colored left accent border matching the metric's severity/status color. Mono label, large numeric value, optional trend indicator.
### Data Table (`.data-table`)
Sticky header with `backdrop-blur-sm`. Mono uppercase column headers at `text-xxs`. Dividing lines via `divide-y divide-zinc-900`. Link style: `underline decoration-zinc-700 hover:decoration-zinc-500`.
### Badges
Severity: 3-letter mono labels with colored border/bg at low opacity. Scan status: similar pattern with semantic colors. Finding status: short labels (Open, Ack, Fixed, FP) in zinc tones.
### Pagination (`.pagination`)
Compact mono-spaced page numbers. Active page: `bg-zinc-800 text-zinc-100 border border-zinc-700`. Disabled: `text-zinc-700`.
## Animations
- **Integrity pulse**: 2s ease-in-out infinite on system status dot
- **Sidebar transition**: `width 0.15s ease`
- **Processing spinner**: `animate-spin` on `hero-arrow-path` icon
## Scrollbar
Custom webkit scrollbar: 6px width, `zinc-950` track, `zinc-800` thumb, `zinc-700` hover.
## Accessibility
- WCAG 2.2 AA compliance
- `aria-current="page"` on active nav item
- `aria-label` on icon-only buttons
- `aria-live="polite"` on flash container
- Keyboard navigable sidebar and triage actions
- Focus-visible outlines on interactive elements
+6
View File
@@ -88,6 +88,12 @@
animation: integrity-pulse 2s ease-in-out infinite;
}
/* Sidebar collapse/expand controlled via data-expanded attribute (set by JS hook) */
#sidebar { width: 52px; }
#sidebar[data-expanded="true"] { width: 180px; }
#sidebar .sidebar-label { display: none; }
#sidebar[data-expanded="true"] .sidebar-label { display: inline; }
/* Mobile sidebar override: always expanded when visible */
@media (max-width: 1023px) {
#sidebar { width: 14rem !important; }
+1 -4
View File
@@ -18,10 +18,7 @@ const Sidebar = {
},
applyState() {
this.el.style.width = this.expanded ? "180px" : "52px"
this.el.querySelectorAll(".sidebar-label").forEach(el => {
el.classList.toggle("hidden", !this.expanded)
})
this.el.dataset.expanded = String(this.expanded)
}
}
-505
View File
@@ -1,505 +0,0 @@
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bulwark — Security Operations Center</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['"JetBrains Mono"', '"SF Mono"', 'Menlo', 'monospace'],
},
fontSize: { 'xxs': ['0.625rem', '0.875rem'] },
colors: {
sev: {
critical: '#ef4444',
high: '#f97316',
medium: '#eab308',
low: '#22c55e',
info: '#71717a',
}
}
}
}
}
</script>
<style>
* { box-sizing: border-box; }
body { overflow: hidden; }
.dot-grid {
background-image: radial-gradient(circle, rgba(255,255,255,0.025) 1px, transparent 1px);
background-size: 20px 20px;
}
@keyframes integrity-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(0.85); }
}
.integrity-dot { animation: integrity-pulse 2s ease-in-out infinite; }
@keyframes cursor-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.cmd-cursor::after {
content: '';
display: inline-block;
width: 1px;
height: 14px;
background: #a1a1aa;
margin-left: 1px;
vertical-align: text-bottom;
animation: cursor-blink 1s step-end infinite;
}
input:focus + .cmd-cursor::after,
.filter-input:focus::after { display: none; }
.filter-input:focus { outline: none; }
.filter-input::placeholder { color: #52525b; }
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: #09090b; }
::-webkit-scrollbar-thumb { background: #27272a; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #3f3f46; }
.row-expand { max-height: 0; overflow: hidden; transition: max-height 0.2s ease-out; }
.row-expand.open { max-height: 300px; }
.sidebar-transition { transition: width 0.15s ease; }
.raw-view { display: none; }
.raw-view.active { display: block; }
.table-view.hidden { display: none; }
.json-key { color: #a1a1aa; }
.json-str { color: #d4d4d8; }
.json-num { color: #a1a1aa; }
.json-sev-critical { color: #ef4444; }
.json-sev-high { color: #f97316; }
.json-sev-medium { color: #eab308; }
.json-bracket { color: #52525b; }
tr.finding-row { cursor: pointer; }
tr.finding-row:hover { background: rgba(255,255,255,0.02); }
tr.finding-row:hover td:first-child .sev-indicator {
box-shadow: 0 0 6px var(--sev-color);
}
.filter-pill {
transition: background 0.1s ease;
}
.filter-pill:hover {
background: rgba(255,255,255,0.08);
}
</style>
</head>
<body class="bg-zinc-950 text-zinc-50 font-sans antialiased h-screen flex">
<!-- ============ SIDEBAR ============ -->
<aside id="sidebar" class="sidebar-transition flex-shrink-0 border-r border-zinc-800 bg-zinc-950 flex flex-col h-screen" style="width: 52px;">
<!-- Logo -->
<div class="h-12 flex items-center justify-center border-b border-zinc-800 flex-shrink-0">
<div class="flex items-center gap-2 overflow-hidden">
<i data-lucide="shield" class="w-5 h-5 text-zinc-300 flex-shrink-0"></i>
<span class="sidebar-label text-sm font-semibold tracking-tight text-zinc-100 whitespace-nowrap hidden">Bulwark</span>
</div>
</div>
<!-- Navigation -->
<nav class="flex-1 py-3 px-2 space-y-0.5">
<a href="#" class="flex items-center gap-3 px-2 py-2 rounded-md bg-zinc-900 text-zinc-100 group" title="Dashboard">
<i data-lucide="layout-dashboard" class="w-4 h-4 flex-shrink-0"></i>
<span class="sidebar-label text-xs font-medium hidden">Dashboard</span>
</a>
<a href="#" class="flex items-center gap-3 px-2 py-2 rounded-md text-zinc-500 hover:text-zinc-300 hover:bg-zinc-900/50 group" title="Scans">
<i data-lucide="scan-line" class="w-4 h-4 flex-shrink-0"></i>
<span class="sidebar-label text-xs font-medium hidden">Scans</span>
</a>
<a href="#" class="flex items-center gap-3 px-2 py-2 rounded-md text-zinc-500 hover:text-zinc-300 hover:bg-zinc-900/50 group" title="Vulnerabilities">
<i data-lucide="shield-alert" class="w-4 h-4 flex-shrink-0"></i>
<span class="sidebar-label text-xs font-medium hidden">Vulnerabilities</span>
</a>
<a href="#" class="flex items-center gap-3 px-2 py-2 rounded-md text-zinc-500 hover:text-zinc-300 hover:bg-zinc-900/50 group" title="Findings">
<i data-lucide="search" class="w-4 h-4 flex-shrink-0"></i>
<span class="sidebar-label text-xs font-medium hidden">Findings</span>
</a>
<a href="#" class="flex items-center gap-3 px-2 py-2 rounded-md text-zinc-500 hover:text-zinc-300 hover:bg-zinc-900/50 group" title="Assets">
<i data-lucide="server" class="w-4 h-4 flex-shrink-0"></i>
<span class="sidebar-label text-xs font-medium hidden">Assets</span>
</a>
</nav>
<!-- Bottom -->
<div class="border-t border-zinc-800 py-3 px-2 space-y-0.5">
<button id="sidebar-toggle" class="flex items-center gap-3 px-2 py-2 rounded-md text-zinc-500 hover:text-zinc-300 hover:bg-zinc-900/50 w-full" title="Toggle sidebar">
<i data-lucide="panel-left-open" class="w-4 h-4 flex-shrink-0 sidebar-toggle-icon"></i>
<span class="sidebar-label text-xs font-medium hidden">Collapse</span>
</button>
<a href="#" class="flex items-center gap-3 px-2 py-2 rounded-md text-zinc-500 hover:text-zinc-300 hover:bg-zinc-900/50" title="Settings">
<i data-lucide="settings" class="w-4 h-4 flex-shrink-0"></i>
<span class="sidebar-label text-xs font-medium hidden">Settings</span>
</a>
</div>
</aside>
<!-- ============ MAIN ============ -->
<div class="flex-1 flex flex-col min-w-0 h-screen">
<!-- Header -->
<header class="h-12 flex items-center justify-between px-4 border-b border-zinc-800 flex-shrink-0 bg-zinc-950">
<div class="flex items-center gap-2 text-xs">
<span class="text-zinc-500">Bulwark</span>
<i data-lucide="chevron-right" class="w-3 h-3 text-zinc-700"></i>
<span class="text-zinc-300">Findings</span>
<i data-lucide="chevron-right" class="w-3 h-3 text-zinc-700"></i>
<span class="text-zinc-100 font-medium">All Vulnerabilities</span>
</div>
<div class="flex items-center gap-4">
<!-- Raw Source Toggle -->
<button id="raw-toggle" class="flex items-center gap-1.5 px-2 py-1 rounded-md text-zinc-500 hover:text-zinc-300 hover:bg-zinc-900 text-xxs font-mono uppercase tracking-wider transition-colors">
<i data-lucide="terminal" class="w-3 h-3"></i>
<span>Raw Source</span>
</button>
<div class="h-4 w-px bg-zinc-800"></div>
<!-- System Integrity -->
<div class="flex items-center gap-2">
<div class="relative flex items-center justify-center">
<div class="w-2 h-2 rounded-full bg-green-500 integrity-dot"></div>
</div>
<span class="text-xxs font-mono text-zinc-400 uppercase tracking-wider">Systems Nominal</span>
</div>
</div>
</header>
<!-- Filter Bar -->
<div class="h-10 flex items-center border-b border-zinc-800 bg-zinc-950 px-4 gap-3 flex-shrink-0">
<div class="flex items-center gap-1.5 text-zinc-600 flex-shrink-0">
<kbd class="font-mono text-xxs border border-zinc-800 rounded px-1 py-0.5 bg-zinc-900 text-zinc-500">&#8984;K</kbd>
</div>
<div class="h-5 w-px bg-zinc-800 flex-shrink-0"></div>
<div class="flex-1 flex items-center gap-2 min-w-0">
<div class="flex items-center gap-2 flex-shrink-0" id="filter-pills">
<span class="filter-pill inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-red-500/10 border border-red-500/20 text-xxs font-mono text-red-400">
severity:critical
<button class="hover:text-red-300" onclick="this.parentElement.remove()"><i data-lucide="x" class="w-2.5 h-2.5"></i></button>
</span>
<span class="filter-pill inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-zinc-800 border border-zinc-700 text-xxs font-mono text-zinc-400">
source:trivy
<button class="hover:text-zinc-200" onclick="this.parentElement.remove()"><i data-lucide="x" class="w-2.5 h-2.5"></i></button>
</span>
</div>
<div class="flex-1 flex items-center min-w-0">
<input
id="filter-input"
type="text"
class="filter-input bg-transparent text-xs font-mono text-zinc-300 w-full py-1"
placeholder="Filter by CVE, package, severity..."
spellcheck="false"
autocomplete="off"
>
</div>
</div>
<button class="text-xxs font-mono text-zinc-600 hover:text-zinc-400 flex-shrink-0 uppercase tracking-wider" onclick="document.getElementById('filter-pills').innerHTML=''">Clear</button>
</div>
<!-- Table / Content Area -->
<div class="flex-1 overflow-y-auto dot-grid">
<!-- Table View -->
<div id="table-view" class="table-view">
<table class="w-full text-left">
<thead class="sticky top-0 bg-zinc-950/95 backdrop-blur-sm border-b border-zinc-800 z-10">
<tr>
<th class="px-4 py-2 text-xxs font-mono font-medium text-zinc-500 uppercase tracking-wider w-20">Sev</th>
<th class="px-4 py-2 text-xxs font-mono font-medium text-zinc-500 uppercase tracking-wider w-44">CVE ID</th>
<th class="px-4 py-2 text-xxs font-mono font-medium text-zinc-500 uppercase tracking-wider">Package / Artifact</th>
<th class="px-4 py-2 text-xxs font-mono font-medium text-zinc-500 uppercase tracking-wider w-28">Installed</th>
<th class="px-4 py-2 text-xxs font-mono font-medium text-zinc-500 uppercase tracking-wider w-28">Fix</th>
<th class="px-4 py-2 text-xxs font-mono font-medium text-zinc-500 uppercase tracking-wider w-24">Source</th>
<th class="px-4 py-2 text-xxs font-mono font-medium text-zinc-500 uppercase tracking-wider w-24">Discovered</th>
<th class="px-4 py-2 text-xxs font-mono font-medium text-zinc-500 uppercase tracking-wider w-16">Status</th>
<th class="px-4 py-2 w-10"></th>
</tr>
</thead>
<tbody class="divide-y divide-zinc-900" id="findings-tbody">
</tbody>
</table>
</div>
<!-- Raw Source View -->
<div id="raw-view" class="raw-view p-4">
<pre class="font-mono text-xs leading-relaxed" id="raw-json"></pre>
</div>
</div>
<!-- Status Bar -->
<footer class="h-7 flex items-center px-4 border-t border-zinc-800 bg-zinc-950 text-xxs font-mono flex-shrink-0 gap-0">
<div class="flex items-center gap-1.5 text-green-500">
<div class="w-1.5 h-1.5 rounded-full bg-green-500"></div>
<span class="uppercase tracking-wider">Connected</span>
</div>
<div class="mx-3 h-3 w-px bg-zinc-800"></div>
<span class="text-zinc-500">Sync: <span class="text-zinc-400" id="sync-time">3s ago</span></span>
<div class="mx-3 h-3 w-px bg-zinc-800"></div>
<span class="text-zinc-500">Active Scans: <span class="text-zinc-400">3</span></span>
<div class="mx-3 h-3 w-px bg-zinc-800"></div>
<div class="flex items-center gap-3">
<span class="flex items-center gap-1"><span class="w-1.5 h-1.5 rounded-full bg-red-500"></span><span class="text-zinc-400">4</span><span class="text-zinc-600">Critical</span></span>
<span class="flex items-center gap-1"><span class="w-1.5 h-1.5 rounded-full bg-orange-500"></span><span class="text-zinc-400">7</span><span class="text-zinc-600">High</span></span>
<span class="flex items-center gap-1"><span class="w-1.5 h-1.5 rounded-full bg-yellow-500"></span><span class="text-zinc-400">12</span><span class="text-zinc-600">Medium</span></span>
</div>
<div class="flex-1"></div>
<span class="text-zinc-600">Pipeline: <span class="text-green-500 uppercase">Healthy</span></span>
<div class="mx-3 h-3 w-px bg-zinc-800"></div>
<span class="text-zinc-600">Uptime: <span class="text-zinc-400" id="uptime">00:00:00</span></span>
</footer>
</div>
<script>
lucide.createIcons();
// ─── Data ───────────────────────────────────────────────────
const findings = [
{ sev: 'critical', cve: 'CVE-2024-3094', pkg: 'xz-utils', installed: '5.6.0', fix: '5.6.1', src: 'trivy', time: '2h ago', status: 'open', desc: 'Malicious code was discovered in the upstream tarballs of xz, starting with version 5.6.0. The malicious code modifies functions in liblzma to intercept and modify the data interaction with the OpenSSH daemon (sshd).', cvss: '10.0', refs: ['https://nvd.nist.gov/vuln/detail/CVE-2024-3094'] },
{ sev: 'critical', cve: 'CVE-2024-1086', pkg: 'linux-kernel', installed: '6.6.13', fix: '6.6.14', src: 'trivy', time: '4h ago', status: 'open', desc: 'A use-after-free vulnerability in the netfilter subsystem of the Linux kernel can be exploited to achieve local privilege escalation.', cvss: '7.8', refs: [] },
{ sev: 'critical', cve: 'CVE-2024-21626', pkg: 'runc', installed: '1.1.11', fix: '1.1.12', src: 'trivy', time: '6h ago', status: 'ack', desc: 'runc through 1.1.11, as used in Docker, allows container escape via an internal file descriptor leak.', cvss: '8.6', refs: [] },
{ sev: 'critical', cve: 'CVE-2024-27198', pkg: 'teamcity', installed: '2023.11.3', fix: '2023.11.4', src: 'sarif', time: '8h ago', status: 'open', desc: 'Authentication bypass in JetBrains TeamCity allowing unauthenticated attackers to take administrative control.', cvss: '9.8', refs: [] },
{ sev: 'high', cve: 'CVE-2024-0567', pkg: 'gnutls', installed: '3.8.2', fix: '3.8.3', src: 'sarif', time: '10h ago', status: 'open', desc: 'A vulnerability was found in GnuTLS. The response times to malformed ciphertexts in RSA-PSK ClientKeyExchange differ from response times of ciphertexts with correct PKCS#1 v1.5 padding.', cvss: '7.5', refs: [] },
{ sev: 'high', cve: 'CVE-2024-24786', pkg: 'protobuf-go', installed: '1.32.0', fix: '1.33.0', src: 'cyclonedx', time: '12h ago', status: 'open', desc: 'The protojson.Unmarshal function can enter an infinite loop when unmarshaling certain forms of invalid JSON.', cvss: '7.5', refs: [] },
{ sev: 'high', cve: 'CVE-2023-50164', pkg: 'struts2-core', installed: '6.3.0', fix: '6.3.0.2', src: 'sarif', time: '1d ago', status: 'ack', desc: 'An attacker can manipulate file upload params to enable paths traversal and under some circumstances this can lead to uploading a malicious file.', cvss: '9.8', refs: [] },
{ sev: 'high', cve: 'CVE-2023-6879', pkg: 'aom', installed: '3.7.0', fix: '3.7.1', src: 'cyclonedx', time: '2d ago', status: 'open', desc: 'Heap buffer overflow in libaom AV1 codec allows remote code execution via a crafted video file.', cvss: '8.8', refs: [] },
{ sev: 'high', cve: 'CVE-2024-20932', pkg: 'jdk', installed: '21.0.1', fix: '21.0.2', src: 'cyclonedx', time: '2d ago', status: 'open', desc: 'Vulnerability in Oracle Java SE allows unauthenticated attacker with network access via multiple protocols to compromise Java SE.', cvss: '7.5', refs: [] },
{ sev: 'high', cve: 'CVE-2024-23897', pkg: 'jenkins-cli', installed: '2.441', fix: '2.442', src: 'sarif', time: '3d ago', status: 'resolved', desc: 'Jenkins CLI arbitrary file read vulnerability through the args4j library.', cvss: '9.8', refs: [] },
{ sev: 'medium', cve: 'CVE-2024-0853', pkg: 'curl', installed: '8.5.0', fix: '8.6.0', src: 'trivy', time: '3d ago', status: 'open', desc: 'curl inadvertently kept the SSL session ID for connections in its cache even when the verify status check failed.', cvss: '5.3', refs: [] },
{ sev: 'medium', cve: 'CVE-2024-22365', pkg: 'pam', installed: '1.5.2', fix: '1.5.3', src: 'trivy', time: '4d ago', status: 'open', desc: 'A vulnerability in Linux-PAM allows unprivileged local attackers to block another user from changing their password.', cvss: '5.5', refs: [] },
{ sev: 'medium', cve: 'CVE-2024-0727', pkg: 'openssl', installed: '3.2.0', fix: '3.2.1', src: 'trivy', time: '5d ago', status: 'ack', desc: 'Issue in OpenSSL where processing a maliciously formatted PKCS12 file may lead OpenSSL to crash leading to a potential Denial of Service.', cvss: '5.5', refs: [] },
{ sev: 'medium', cve: 'CVE-2023-51385', pkg: 'openssh', installed: '9.5p1', fix: '9.6p1', src: 'trivy', time: '6d ago', status: 'open', desc: 'In ssh in OpenSSH before 9.6, OS command injection might occur if a user name or host name has shell metacharacters.', cvss: '6.5', refs: [] },
{ sev: 'medium', cve: 'CVE-2023-48795', pkg: 'libssh2', installed: '1.11.0', fix: '1.11.1', src: 'sarif', time: '7d ago', status: 'open', desc: 'The SSH transport protocol with certain OpenSSH extensions allows remote attackers to bypass integrity checks (Terrapin attack).', cvss: '5.9', refs: [] },
{ sev: 'medium', cve: 'CVE-2024-0406', pkg: 'mholt/archiver', installed: '3.5.1', fix: '4.0.0', src: 'cyclonedx', time: '8d ago', status: 'open', desc: 'A path traversal vulnerability in archiver allows an attacker to write files outside of the target directory.', cvss: '6.1', refs: [] },
];
// ─── Severity Helpers ───────────────────────────────────────
const sevConfig = {
critical: { label: 'CRT', color: '#ef4444', bg: 'rgba(239,68,68,0.1)', border: 'rgba(239,68,68,0.2)' },
high: { label: 'HI', color: '#f97316', bg: 'rgba(249,115,22,0.1)', border: 'rgba(249,115,22,0.2)' },
medium: { label: 'MED', color: '#eab308', bg: 'rgba(234,179,8,0.1)', border: 'rgba(234,179,8,0.2)' },
low: { label: 'LOW', color: '#22c55e', bg: 'rgba(34,197,94,0.1)', border: 'rgba(34,197,94,0.2)' },
};
const statusConfig = {
open: { label: 'Open', classes: 'text-zinc-400 bg-zinc-800/50 border-zinc-700' },
ack: { label: 'Ack', classes: 'text-zinc-300 bg-zinc-800 border-zinc-600' },
resolved: { label: 'Fixed', classes: 'text-zinc-500 bg-zinc-900 border-zinc-800' },
};
// ─── Render Table ───────────────────────────────────────────
function renderTable() {
const tbody = document.getElementById('findings-tbody');
tbody.innerHTML = '';
findings.forEach((f, i) => {
const sev = sevConfig[f.sev];
const status = statusConfig[f.status] || statusConfig.open;
const tr = document.createElement('tr');
tr.className = 'finding-row group';
tr.style.setProperty('--sev-color', sev.color);
tr.innerHTML = `
<td class="px-4 py-1.5">
<span class="sev-indicator inline-flex items-center gap-1.5 font-mono text-xxs font-medium rounded px-1.5 py-0.5 transition-shadow"
style="color: ${sev.color}; background: ${sev.bg}; border: 1px solid ${sev.border};">
${sev.label}
</span>
</td>
<td class="px-4 py-1.5 font-mono text-xs text-zinc-200 tracking-tight">${f.cve}</td>
<td class="px-4 py-1.5 font-mono text-xs text-zinc-300">${f.pkg}</td>
<td class="px-4 py-1.5 font-mono text-xxs text-zinc-500 tabular-nums">${f.installed}</td>
<td class="px-4 py-1.5 font-mono text-xxs text-zinc-400 tabular-nums">${f.fix}</td>
<td class="px-4 py-1.5">
<span class="font-mono text-xxs text-zinc-500 uppercase tracking-wider">${f.src}</span>
</td>
<td class="px-4 py-1.5 font-mono text-xxs text-zinc-500 tabular-nums">${f.time}</td>
<td class="px-4 py-1.5">
<span class="inline-flex font-mono text-xxs rounded px-1.5 py-0.5 border ${status.classes}">${status.label}</span>
</td>
<td class="px-4 py-1.5">
<button class="text-zinc-600 hover:text-zinc-300 transition-colors p-0.5 rounded hover:bg-zinc-800" title="View metadata" onclick="event.stopPropagation(); toggleRow(${i})">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>
</button>
</td>
`;
tr.addEventListener('click', () => toggleRow(i));
tbody.appendChild(tr);
// Expanded detail row
const detailTr = document.createElement('tr');
detailTr.id = `detail-${i}`;
detailTr.className = 'bg-zinc-950';
detailTr.style.display = 'none';
detailTr.innerHTML = `
<td colspan="9" class="px-4 py-0">
<div class="row-expand" id="detail-content-${i}">
<div class="border border-zinc-800 rounded-md my-2 bg-zinc-900/50">
<div class="grid grid-cols-[120px_1fr] gap-x-4 gap-y-2 p-3 text-xs">
<span class="font-mono text-xxs text-zinc-500 uppercase tracking-wider">Description</span>
<span class="text-zinc-300 leading-relaxed">${f.desc}</span>
<span class="font-mono text-xxs text-zinc-500 uppercase tracking-wider">CVSS Score</span>
<span class="font-mono text-zinc-200">${f.cvss}</span>
<span class="font-mono text-xxs text-zinc-500 uppercase tracking-wider">Installed</span>
<span class="font-mono text-zinc-400">${f.installed} &rarr; ${f.fix}</span>
<span class="font-mono text-xxs text-zinc-500 uppercase tracking-wider">Artifact</span>
<span class="font-mono text-zinc-400">${f.pkg}@${f.installed}</span>
<span class="font-mono text-xxs text-zinc-500 uppercase tracking-wider">Scanner</span>
<span class="font-mono text-zinc-400 uppercase">${f.src}</span>
</div>
</div>
</div>
</td>
`;
tbody.appendChild(detailTr);
});
}
function toggleRow(i) {
const detailTr = document.getElementById(`detail-${i}`);
const content = document.getElementById(`detail-content-${i}`);
if (detailTr.style.display === 'none') {
// Close all others first
document.querySelectorAll('[id^="detail-"]:not([id*="content"])').forEach(el => {
el.style.display = 'none';
});
document.querySelectorAll('.row-expand').forEach(el => el.classList.remove('open'));
detailTr.style.display = 'table-row';
requestAnimationFrame(() => content.classList.add('open'));
} else {
content.classList.remove('open');
setTimeout(() => { detailTr.style.display = 'none'; }, 200);
}
}
// ─── Raw Source View ────────────────────────────────────────
function renderRawJSON() {
const data = {
scan_id: "scan_7f3a9c2e",
scanner: "trivy",
format: "json",
timestamp: "2024-12-15T14:32:00Z",
target: "registry.internal/app:latest",
findings: findings.map(f => ({
vulnerability_id: f.cve,
severity: f.sev.toUpperCase(),
package: f.pkg,
installed_version: f.installed,
fixed_version: f.fix,
status: f.status,
title: f.desc.substring(0, 80) + '...'
}))
};
const json = JSON.stringify(data, null, 2);
const highlighted = json
.replace(/"([^"]+)":/g, '<span class="json-key">"$1"</span>:')
.replace(/: "CRITICAL"/g, ': <span class="json-sev-critical">"CRITICAL"</span>')
.replace(/: "HIGH"/g, ': <span class="json-sev-high">"HIGH"</span>')
.replace(/: "MEDIUM"/g, ': <span class="json-sev-medium">"MEDIUM"</span>')
.replace(/: "((?!CRITICAL|HIGH|MEDIUM)[^"]*?)"/g, ': <span class="json-str">"$1"</span>')
.replace(/: (\d+)/g, ': <span class="json-num">$1</span>')
.replace(/[\[\]{}]/g, '<span class="json-bracket">$&</span>');
document.getElementById('raw-json').innerHTML = highlighted;
}
// ─── Sidebar Toggle ────────────────────────────────────────
let sidebarExpanded = false;
document.getElementById('sidebar-toggle').addEventListener('click', () => {
sidebarExpanded = !sidebarExpanded;
const sidebar = document.getElementById('sidebar');
sidebar.style.width = sidebarExpanded ? '180px' : '52px';
document.querySelectorAll('.sidebar-label').forEach(el => {
el.classList.toggle('hidden', !sidebarExpanded);
});
const icon = document.querySelector('.sidebar-toggle-icon');
if (sidebarExpanded) {
icon.setAttribute('data-lucide', 'panel-left-close');
} else {
icon.setAttribute('data-lucide', 'panel-left-open');
}
lucide.createIcons();
});
// ─── Raw Source Toggle ─────────────────────────────────────
let rawMode = false;
document.getElementById('raw-toggle').addEventListener('click', () => {
rawMode = !rawMode;
const btn = document.getElementById('raw-toggle');
const tableView = document.getElementById('table-view');
const rawView = document.getElementById('raw-view');
if (rawMode) {
tableView.classList.add('hidden');
rawView.classList.add('active');
btn.classList.add('bg-zinc-800', 'text-zinc-200');
btn.classList.remove('text-zinc-500');
} else {
tableView.classList.remove('hidden');
rawView.classList.remove('active');
btn.classList.remove('bg-zinc-800', 'text-zinc-200');
btn.classList.add('text-zinc-500');
}
});
// ─── Keyboard Shortcuts ────────────────────────────────────
document.addEventListener('keydown', (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
document.getElementById('filter-input').focus();
}
if (e.key === 'Escape') {
document.getElementById('filter-input').blur();
}
});
// ─── Sync Timer ────────────────────────────────────────────
let syncSeconds = 3;
setInterval(() => {
syncSeconds++;
const el = document.getElementById('sync-time');
if (syncSeconds < 60) {
el.textContent = `${syncSeconds}s ago`;
} else {
el.textContent = `${Math.floor(syncSeconds / 60)}m ago`;
}
}, 1000);
// ─── Uptime Counter ────────────────────────────────────────
const startTime = Date.now();
setInterval(() => {
const elapsed = Math.floor((Date.now() - startTime) / 1000);
const h = String(Math.floor(elapsed / 3600)).padStart(2, '0');
const m = String(Math.floor((elapsed % 3600) / 60)).padStart(2, '0');
const s = String(elapsed % 60).padStart(2, '0');
document.getElementById('uptime').textContent = `${h}:${m}:${s}`;
}, 1000);
// ─── Init ──────────────────────────────────────────────────
renderTable();
renderRawJSON();
</script>
</body>
</html>
+4
View File
@@ -41,6 +41,10 @@ defmodule SecDashboard.Ingestion.Detector do
def detect(%{"spdxVersion" => _}), do: {"spdx", "json"}
def detect(%{"matches" => _, "source" => _}), do: {"grype", "json"}
def detect(%{"vulnerabilities" => _, "packageManager" => _}), do: {"snyk", "json"}
def detect(data) when is_map(data) do
cond do
Map.has_key?(data, "runs") -> {infer_sarif_tool(data), "sarif"}
+14
View File
@@ -18,6 +18,14 @@ defmodule SecDashboard.Ingestion.ParseJob do
def perform(%Oban.Job{args: %{"scan_id" => scan_id}}) do
scan = Security.get_scan!(scan_id)
if scan.status == "cancelled" do
:ok
else
run_pipeline(scan)
end
end
defp run_pipeline(scan) do
with {:ok, scan} <- mark_processing(scan),
{:ok, raw} <- read_file(scan.file_path),
{:ok, data} <- decode(raw, scan.source_format),
@@ -61,6 +69,9 @@ defmodule SecDashboard.Ingestion.ParseJob do
defp dispatch_parser(data, "trivy"), do: Parsers.Trivy.parse(data)
defp dispatch_parser(data, "cyclonedx"), do: Parsers.CycloneDX.parse(data)
defp dispatch_parser(data, "grype"), do: Parsers.Grype.parse(data)
defp dispatch_parser(data, "snyk"), do: Parsers.Snyk.parse(data)
defp dispatch_parser(_data, "spdx"), do: {:ok, []}
defp dispatch_parser(data, _tool) do
{detected_tool, _format} = Detector.detect(data)
@@ -69,6 +80,9 @@ defmodule SecDashboard.Ingestion.ParseJob do
defp dispatch_by_detected(data, "trivy"), do: Parsers.Trivy.parse(data)
defp dispatch_by_detected(data, "cyclonedx"), do: Parsers.CycloneDX.parse(data)
defp dispatch_by_detected(data, "grype"), do: Parsers.Grype.parse(data)
defp dispatch_by_detected(data, "snyk"), do: Parsers.Snyk.parse(data)
defp dispatch_by_detected(_data, "spdx"), do: {:ok, []}
defp dispatch_by_detected(data, _), do: Parsers.Sarif.parse(data)
defp persist_findings(scan, findings) do
@@ -0,0 +1,86 @@
defmodule SecDashboard.Ingestion.Parsers.Grype do
@moduledoc """
Parser for Anchore Grype JSON scan reports.
Normalizes Grype's `matches` array into `t:SecDashboard.Ingestion.Parser.normalized_finding/0`
maps, extracting vulnerability IDs, severity, affected packages, and fix versions
from the Grype-specific match/artifact structure.
"""
@behaviour SecDashboard.Ingestion.Parser
@impl true
def parse(%{"matches" => matches} = report) when is_list(matches) do
source = report["source"] || %{}
asset_meta = extract_asset_meta(source)
findings = Enum.map(matches, fn m -> parse_match(m, asset_meta) end)
{:ok, findings}
end
def parse(_), do: {:error, "invalid Grype report: missing matches key"}
defp parse_match(match, asset_meta) do
vuln = match["vulnerability"] || %{}
artifact = match["artifact"] || %{}
related = match["relatedVulnerabilities"] || []
%{
asset: %{
type: asset_meta.type,
identifier: asset_meta.identifier,
metadata: asset_meta.metadata
},
vulnerability: %{
external_id: vuln["id"] || "unknown",
severity: normalize_severity(vuln["severity"]),
title: extract_title(vuln, related),
description: extract_description(vuln, related),
references: extract_urls(vuln)
},
location: artifact["name"],
installed_version: artifact["version"],
fixed_version: extract_fixed_version(vuln),
raw_data: match
}
end
defp extract_asset_meta(%{"type" => type, "target" => target}) do
%{
type: map_source_type(type),
identifier: extract_identifier(target),
metadata: %{"sourceType" => type}
}
end
defp extract_asset_meta(_) do
%{type: "package", identifier: "unknown", metadata: %{}}
end
defp extract_identifier(target) when is_binary(target), do: target
defp extract_identifier(%{"userInput" => input}), do: input
defp extract_identifier(_), do: "unknown"
defp map_source_type("image"), do: "container_image"
defp map_source_type("directory"), do: "filesystem"
defp map_source_type(_), do: "package"
defp normalize_severity(sev) when is_binary(sev), do: String.downcase(sev)
defp normalize_severity(_), do: "info"
defp extract_title(vuln, [related | _]) do
get_in(related, ["description"]) || vuln["description"]
end
defp extract_title(vuln, _), do: vuln["description"]
defp extract_description(vuln, related) do
Enum.find_value(related, vuln["description"], fn r -> r["description"] end)
end
defp extract_urls(%{"urls" => urls}) when is_list(urls), do: urls
defp extract_urls(%{"dataSource" => ds}) when is_binary(ds), do: [ds]
defp extract_urls(_), do: []
defp extract_fixed_version(%{"fix" => %{"versions" => [v | _]}}), do: v
defp extract_fixed_version(_), do: nil
end
@@ -0,0 +1,73 @@
defmodule SecDashboard.Ingestion.Parsers.Snyk do
@moduledoc """
Parser for Snyk JSON test output (`snyk test --json`).
Normalizes Snyk's `vulnerabilities` array into
`t:SecDashboard.Ingestion.Parser.normalized_finding/0` maps, extracting
CVE/SNYK IDs, severity, upgrade paths, and package version info.
"""
@behaviour SecDashboard.Ingestion.Parser
@impl true
def parse(%{"vulnerabilities" => vulns} = report) when is_list(vulns) do
project = report["projectName"] || report["path"] || "unknown"
pkg_manager = report["packageManager"] || "unknown"
findings =
vulns
|> Enum.map(fn v -> parse_vuln(v, project, pkg_manager) end)
{:ok, findings}
end
def parse(_), do: {:error, "invalid Snyk report: missing vulnerabilities key"}
defp parse_vuln(vuln, project, pkg_manager) do
%{
asset: %{
type: "package",
identifier: project,
metadata: %{"packageManager" => pkg_manager}
},
vulnerability: %{
external_id: primary_id(vuln),
severity: normalize_severity(vuln["severity"]),
title: vuln["title"],
description: vuln["description"],
references: extract_references(vuln)
},
location: vuln["moduleName"] || vuln["packageName"],
installed_version: vuln["version"],
fixed_version: extract_fix(vuln),
raw_data: vuln
}
end
defp primary_id(%{"identifiers" => %{"CVE" => [cve | _]}}), do: cve
defp primary_id(%{"id" => id}) when is_binary(id), do: id
defp primary_id(_), do: "unknown"
defp normalize_severity(sev) when is_binary(sev), do: String.downcase(sev)
defp normalize_severity(_), do: "info"
defp extract_references(%{"references" => refs}) when is_list(refs) do
Enum.flat_map(refs, fn
%{"url" => url} -> [url]
_ -> []
end)
end
defp extract_references(%{"url" => url}) when is_binary(url), do: [url]
defp extract_references(_), do: []
defp extract_fix(%{"fixedIn" => [v | _]}), do: v
defp extract_fix(%{"upgradePath" => [_ | [upgrade | _]]}) when is_binary(upgrade) do
case String.split(upgrade, "@") do
[_, version] -> version
_ -> nil
end
end
defp extract_fix(_), do: nil
end
+16
View File
@@ -67,6 +67,22 @@ defmodule SecDashboard.Security do
|> Repo.all()
end
@doc """
Cancels a scan that is still `pending` or `processing`.
Returns `{:error, :not_cancellable}` if the scan has already completed or failed.
Broadcasts a scan update on success.
"""
@spec cancel_scan(Scan.t()) :: {:ok, Scan.t()} | {:error, :not_cancellable}
def cancel_scan(%Scan{status: status} = scan) when status in ~w(pending processing) do
{:ok, scan} = update_scan_status(scan, "cancelled", %{completed_at: DateTime.utc_now()})
Phoenix.PubSub.broadcast(SecDashboard.PubSub, "scans", {:scan_updated, scan})
Phoenix.PubSub.broadcast(SecDashboard.PubSub, "scan:#{scan.id}", {:scan_updated, scan})
{:ok, scan}
end
def cancel_scan(_scan), do: {:error, :not_cancellable}
# --- Assets ---
@doc """
+3 -2
View File
@@ -30,6 +30,7 @@ defmodule SecDashboard.Security.Finding do
timestamps(type: :utc_datetime)
end
@required ~w(scan_id vulnerability_id asset_id)a
@optional ~w(location installed_version fixed_version raw_data)a
@doc """
@@ -37,8 +38,8 @@ defmodule SecDashboard.Security.Finding do
"""
def changeset(finding, attrs) do
finding
|> cast(attrs, @optional)
|> validate_required([:scan_id, :vulnerability_id, :asset_id])
|> cast(attrs, @required ++ @optional)
|> validate_required(@required)
|> validate_inclusion(:status, ~w(open acknowledged resolved false_positive))
|> foreign_key_constraint(:scan_id)
|> foreign_key_constraint(:vulnerability_id)
+1 -1
View File
@@ -52,6 +52,6 @@ defmodule SecDashboard.Security.Scan do
scan
|> cast(attrs, ~w(error_message finding_count started_at completed_at)a)
|> put_change(:status, status)
|> validate_inclusion(:status, ~w(pending processing completed failed))
|> validate_inclusion(:status, ~w(pending processing completed failed cancelled))
end
end
@@ -472,7 +472,7 @@ defmodule SecDashboardWeb.CoreComponents do
## Examples
<.stat_card label="Open Findings" value={42} />
<.stat_card label="Critical" value={5} class="border-l-2 border-red-500" />
<.stat_card label="Critical" value={5} class="border-l-2 border-l-red-500" />
"""
attr :label, :string, required: true, doc: "the stat description"
@@ -578,6 +578,9 @@ defmodule SecDashboardWeb.CoreComponents do
defp scan_status_class("failed"),
do: "border-red-500/20 bg-red-500/10 text-red-500"
defp scan_status_class("cancelled"),
do: "border-zinc-600/20 bg-zinc-800/50 text-zinc-500 line-through"
defp scan_status_class(_),
do: "border-zinc-700 bg-zinc-800/50 text-zinc-400"
+3 -4
View File
@@ -55,7 +55,6 @@ defmodule SecDashboardWeb.Layouts do
id="sidebar"
phx-hook="Sidebar"
class="sidebar-transition fixed inset-y-0 left-0 z-40 flex shrink-0 -translate-x-full flex-col border-r border-zinc-800 bg-zinc-950 peer-checked:translate-x-0 lg:relative lg:translate-x-0"
style="width: 52px;"
>
<.sidebar active_section={@active_section} />
</aside>
@@ -102,7 +101,7 @@ defmodule SecDashboardWeb.Layouts do
<div class="flex h-12 shrink-0 items-center justify-center border-b border-zinc-800">
<.link navigate={~p"/"} class="flex items-center gap-2 overflow-hidden">
<.icon name="hero-shield-check" class="size-5 shrink-0 text-zinc-300" />
<span class="sidebar-label hidden whitespace-nowrap text-sm font-semibold tracking-tight text-zinc-100">
<span class="sidebar-label whitespace-nowrap text-sm font-semibold tracking-tight text-zinc-100">
Bulwark
</span>
</.link>
@@ -118,7 +117,7 @@ defmodule SecDashboardWeb.Layouts do
phx-click={JS.dispatch("click", to: "#mobile-nav")}
>
<.icon name={item.icon} class="size-4 shrink-0" />
<span class="sidebar-label hidden text-xs font-medium">{item.label}</span>
<span class="sidebar-label text-xs font-medium">{item.label}</span>
</.link>
</nav>
@@ -129,7 +128,7 @@ defmodule SecDashboardWeb.Layouts do
title="Toggle sidebar"
>
<.icon name="hero-bars-3-bottom-left" class="size-4 shrink-0" />
<span class="sidebar-label hidden text-xs font-medium">Collapse</span>
<span class="sidebar-label text-xs font-medium">Collapse</span>
</button>
</div>
</div>
+9 -5
View File
@@ -65,26 +65,30 @@ defmodule SecDashboardWeb.DashboardLive do
</header>
<div class="grid grid-cols-2 gap-3 md:grid-cols-5">
<.stat_card label="Open Findings" value={@open_findings} />
<.stat_card
label="Open Findings"
value={@open_findings}
class="border-l-2 border-l-sky-500"
/>
<.stat_card
label="Critical"
value={Map.get(@severity_counts, "critical", 0)}
class="border-l-2 border-red-500"
class="border-l-2 border-l-red-500"
/>
<.stat_card
label="High"
value={Map.get(@severity_counts, "high", 0)}
class="border-l-2 border-orange-500"
class="border-l-2 border-l-orange-500"
/>
<.stat_card
label="Medium"
value={Map.get(@severity_counts, "medium", 0)}
class="border-l-2 border-amber-500"
class="border-l-2 border-l-yellow-500"
/>
<.stat_card
label="Low"
value={Map.get(@severity_counts, "low", 0)}
class="border-l-2 border-emerald-500"
class="border-l-2 border-l-emerald-500"
/>
</div>
@@ -89,6 +89,18 @@ defmodule SecDashboardWeb.ScanLive.Index do
{:noreply, push_patch(socket, to: ~p"/scans")}
end
def handle_event("cancel-scan", %{"id" => id}, socket) do
scan = Security.get_scan!(id)
case Security.cancel_scan(scan) do
{:ok, _} ->
{:noreply, put_flash(socket, :info, "Scan cancelled")}
{:error, :not_cancellable} ->
{:noreply, put_flash(socket, :error, "Scan cannot be cancelled")}
end
end
@impl true
def handle_info({:scan_updated, _scan}, socket) do
{:noreply, push_patch(socket, to: ~p"/scans")}
@@ -192,6 +204,16 @@ defmodule SecDashboardWeb.ScanLive.Index do
{Calendar.strftime(scan.inserted_at, "%Y-%m-%d %H:%M")}
</span>
</:col>
<:col :let={scan} label="">
<button
:if={scan.status in ~w(pending processing)}
phx-click="cancel-scan"
phx-value-id={scan.id}
class="rounded px-1.5 py-0.5 text-xxs font-mono text-zinc-500 hover:bg-zinc-800 hover:text-red-400"
>
Cancel
</button>
</:col>
</Flop.Phoenix.table>
</div>
+21 -8
View File
@@ -1,16 +1,29 @@
# Security Tool Sample Outputs
This directory contains sample security scan reports for testing the ingestion system.
Production-grade sample security scan reports for testing the ingestion pipeline.
## Files
- `trivy.json`: A Trivy (v2) container scan report.
- `sarif.json`: A SARIF (v2.1.0) static analysis report (Semgrep style).
- `cyclonedx.json`: A CycloneDX (v1.4) Software Bill of Materials (SBOM) with vulnerabilities.
- `spdx.json`: An SPDX (v2.3) SBOM.
- `gitleaks.sarif.json`: A Gitleaks secret scan report in SARIF format.
- `checkov.sarif.json`: A Checkov IaC scan report in SARIF format.
| File | Tool | Format | Expected Findings |
|------|------|--------|-------------------|
| `trivy.json` | Trivy v2 | JSON | ~20 (container image scan) |
| `sarif.json` | Semgrep | SARIF v2.1.0 | ~15 (Python Flask SAST) |
| `cyclonedx.json` | CycloneDX | JSON v1.5 | ~12 (Node.js SCA) |
| `grype.json` | Grype | JSON | ~12 (container image scan) |
| `snyk.json` | Snyk | JSON | ~10 (npm SCA) |
| `checkov.sarif.json` | Checkov | SARIF v2.1.0 | ~8 (Terraform IaC) |
| `gitleaks.sarif.json` | Gitleaks | SARIF v2.1.0 | ~5 (secret detection) |
| `spdx.json` | SPDX | JSON v2.3 | 0 (BOM only, no vulns) |
## Usage
These files can be used to verify the parsers in `lib/sec_dashboard/ingestion/parsers/`.
Upload via `/scans` or test directly:
```bash
mix run -e '
raw = File.read!("samples/trivy.json")
data = Jason.decode!(raw)
{tool, format} = SecDashboard.Ingestion.Detector.detect(data)
IO.inspect({tool, format})
'
```
+55 -24
View File
@@ -5,37 +5,68 @@
{
"tool": {
"driver": {
"name": "Checkov",
"name": "checkov",
"version": "3.1.40",
"rules": [
{
"id": "CKV_AWS_1",
"shortDescription": {
"text": "Ensure all data stored in the S3 bucket is securely encrypted at rest"
}
}
{ "id": "CKV_AWS_18", "shortDescription": { "text": "Ensure the S3 bucket has access logging enabled" }, "helpUri": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/s3-13-enable-logging" },
{ "id": "CKV_AWS_145", "shortDescription": { "text": "Ensure that S3 buckets are encrypted with KMS by default" }, "helpUri": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-general-policies/ensure-that-s3-buckets-are-encrypted-with-kms-by-default" },
{ "id": "CKV_AWS_21", "shortDescription": { "text": "Ensure the S3 bucket has versioning enabled" }, "helpUri": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/s3-16-enable-versioning" },
{ "id": "CKV_AWS_23", "shortDescription": { "text": "Ensure every security group and rule has a description" }, "helpUri": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/networking-31" },
{ "id": "CKV_AWS_79", "shortDescription": { "text": "Ensure Instance Metadata Service Version 1 is not enabled" }, "helpUri": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-general-policies/bc-aws-general-31" },
{ "id": "CKV_AWS_88", "shortDescription": { "text": "EC2 instance should not have public IP" }, "helpUri": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/ensure-aws-ec2-instance-does-not-have-a-public-ip" },
{ "id": "CKV_AWS_260", "shortDescription": { "text": "Ensure no security group allows ingress from 0.0.0.0/0 to port 80" }, "helpUri": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/ensure-aws-security-group-does-not-allow-ingress-from-0000-to-port-80" },
{ "id": "CKV2_AWS_5", "shortDescription": { "text": "Ensure that Security Groups are attached to an ENI or EC2 instance" }, "helpUri": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/ensure-that-security-groups-are-attached-to-ec2-instances-or-elastic-network-interfaces-enis" }
]
}
},
"results": [
{
"ruleId": "CKV_AWS_1",
"ruleId": "CKV_AWS_18",
"level": "warning",
"message": { "text": "S3 bucket 'data-lake-raw' does not have access logging enabled" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "modules/storage/main.tf" }, "region": { "startLine": 12 } } }]
},
{
"ruleId": "CKV_AWS_145",
"level": "warning",
"message": { "text": "S3 bucket 'data-lake-raw' is not encrypted with KMS" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "modules/storage/main.tf" }, "region": { "startLine": 12 } } }]
},
{
"ruleId": "CKV_AWS_21",
"level": "warning",
"message": { "text": "S3 bucket 'data-lake-raw' does not have versioning enabled" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "modules/storage/main.tf" }, "region": { "startLine": 12 } } }]
},
{
"ruleId": "CKV_AWS_23",
"level": "note",
"message": { "text": "Security group 'api-sg' does not have a description" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "modules/networking/security_groups.tf" }, "region": { "startLine": 1 } } }]
},
{
"ruleId": "CKV_AWS_79",
"level": "error",
"message": {
"text": "S3 bucket is not encrypted at rest"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "infrastructure/s3.tf"
},
"region": {
"startLine": 1,
"endLine": 10
}
}
}
]
"message": { "text": "EC2 instance 'worker-node' has IMDSv1 enabled" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "modules/compute/main.tf" }, "region": { "startLine": 45 } } }]
},
{
"ruleId": "CKV_AWS_88",
"level": "error",
"message": { "text": "EC2 instance 'bastion' has a public IP address assigned" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "modules/compute/bastion.tf" }, "region": { "startLine": 8 } } }]
},
{
"ruleId": "CKV_AWS_260",
"level": "error",
"message": { "text": "Security group 'api-sg' allows ingress from 0.0.0.0/0 to port 80" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "modules/networking/security_groups.tf" }, "region": { "startLine": 15 } } }]
},
{
"ruleId": "CKV2_AWS_5",
"level": "note",
"message": { "text": "Security group 'legacy-db-sg' is not attached to any ENI or EC2 instance" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "modules/networking/security_groups.tf" }, "region": { "startLine": 42 } } }]
}
]
}
+178 -34
View File
@@ -1,44 +1,188 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"serialNumber": "urn:uuid:550e8400-e29b-41d4-a716-446655440000",
"specVersion": "1.5",
"serialNumber": "urn:uuid:f2c8d4e1-7b3a-4c6f-9d5e-1a2b3c4d5e6f",
"version": 1,
"metadata": {
"timestamp": "2026-05-13T12:00:00Z",
"component": {"name": "monolith-app", "version": "1.0.0", "type": "application"}
"timestamp": "2024-11-15T10:30:00Z",
"component": {
"name": "frontend-app",
"type": "application",
"version": "3.1.0",
"purl": "pkg:npm/frontend-app@3.1.0"
},
"tools": [
{ "vendor": "CycloneDX", "name": "cdxgen", "version": "10.2.3" }
]
},
"components": [
{"name": "phoenix", "version": "1.7.0", "bom-ref": "pkg:hex/phoenix@1.7.0"},
{"name": "ecto", "version": "3.9.0", "bom-ref": "pkg:hex/ecto@3.9.0"},
{"name": "jason", "version": "1.4.0", "bom-ref": "pkg:hex/jason@1.4.0"},
{"name": "plug", "version": "1.13.0", "bom-ref": "pkg:hex/plug@1.13.0"},
{"name": "gettext", "version": "0.20.0", "bom-ref": "pkg:hex/gettext@0.20.0"},
{"name": "telemetry", "version": "1.1.0", "bom-ref": "pkg:hex/telemetry@1.1.0"},
{"name": "httpoison", "version": "1.8.0", "bom-ref": "pkg:hex/httpoison@1.8.0"},
{"name": "finch", "version": "0.13.0", "bom-ref": "pkg:hex/finch@0.13.0"},
{"name": "floki", "version": "0.33.0", "bom-ref": "pkg:hex/floki@0.33.0"},
{"name": "esbuild", "version": "0.14.0", "bom-ref": "pkg:hex/esbuild@0.14.0"},
{"name": "swoosh", "version": "1.8.0", "bom-ref": "pkg:hex/swoosh@1.8.0"},
{"name": "oban", "version": "2.13.0", "bom-ref": "pkg:hex/oban@2.13.0"},
{"name": "guardian", "version": "2.3.0", "bom-ref": "pkg:hex/guardian@2.3.0"},
{"name": "absinthe", "version": "1.7.0", "bom-ref": "pkg:hex/absinthe@1.7.0"},
{"name": "cors_plug", "version": "3.0.0", "bom-ref": "pkg:hex/cors_plug@3.0.0"}
{
"type": "library",
"name": "axios",
"version": "0.21.1",
"purl": "pkg:npm/axios@0.21.1",
"bom-ref": "axios@0.21.1"
},
{
"type": "library",
"name": "webpack",
"version": "5.75.0",
"purl": "pkg:npm/webpack@5.75.0",
"bom-ref": "webpack@5.75.0"
},
{
"type": "library",
"name": "react",
"version": "18.2.0",
"purl": "pkg:npm/react@18.2.0",
"bom-ref": "react@18.2.0"
},
{
"type": "library",
"name": "lodash",
"version": "4.17.20",
"purl": "pkg:npm/lodash@4.17.20",
"bom-ref": "lodash@4.17.20"
},
{
"type": "library",
"name": "moment",
"version": "2.29.3",
"purl": "pkg:npm/moment@2.29.3",
"bom-ref": "moment@2.29.3"
},
{
"type": "library",
"name": "node-forge",
"version": "1.3.0",
"purl": "pkg:npm/node-forge@1.3.0",
"bom-ref": "node-forge@1.3.0"
},
{
"type": "library",
"name": "jsonwebtoken",
"version": "8.5.1",
"purl": "pkg:npm/jsonwebtoken@8.5.1",
"bom-ref": "jsonwebtoken@8.5.1"
},
{
"type": "library",
"name": "postcss",
"version": "8.4.21",
"purl": "pkg:npm/postcss@8.4.21",
"bom-ref": "postcss@8.4.21"
},
{
"type": "library",
"name": "follow-redirects",
"version": "1.14.7",
"purl": "pkg:npm/follow-redirects@1.14.7",
"bom-ref": "follow-redirects@1.14.7"
},
{
"type": "library",
"name": "json5",
"version": "2.2.1",
"purl": "pkg:npm/json5@2.2.1",
"bom-ref": "json5@2.2.1"
}
],
"vulnerabilities": [
{"id": "CVE-2024-1001", "description": "Critical XSS", "ratings": [{"severity": "CRITICAL"}], "affects": [{"ref": "pkg:hex/phoenix@1.7.0"}]},
{"id": "CVE-2024-1002", "description": "SQL Injection", "ratings": [{"severity": "CRITICAL"}], "affects": [{"ref": "pkg:hex/ecto@3.9.0"}]},
{"id": "CVE-2024-1003", "description": "DoS in JSON parser", "ratings": [{"severity": "HIGH"}], "affects": [{"ref": "pkg:hex/jason@1.4.0"}]},
{"id": "CVE-2024-1004", "description": "Insecure cookie handling", "ratings": [{"severity": "HIGH"}], "affects": [{"ref": "pkg:hex/plug@1.13.0"}]},
{"id": "CVE-2024-1005", "description": "Path traversal in telemetry", "ratings": [{"severity": "MEDIUM"}], "affects": [{"ref": "pkg:hex/telemetry@1.1.0"}]},
{"id": "CVE-2024-1006", "description": "RCE in HTTP client", "ratings": [{"severity": "CRITICAL"}], "affects": [{"ref": "pkg:hex/httpoison@1.8.0"}]},
{"id": "CVE-2024-1007", "description": "Memory leak in parser", "ratings": [{"severity": "LOW"}], "affects": [{"ref": "pkg:hex/floki@0.33.0"}]},
{"id": "CVE-2024-1008", "description": "Weak JWT validation", "ratings": [{"severity": "HIGH"}], "affects": [{"ref": "pkg:hex/guardian@2.3.0"}]},
{"id": "CVE-2024-1009", "description": "GraphQL complexity DoS", "ratings": [{"severity": "MEDIUM"}], "affects": [{"ref": "pkg:hex/absinthe@1.7.0"}]},
{"id": "CVE-2024-1010", "description": "Insecure CORS policy", "ratings": [{"severity": "LOW"}], "affects": [{"ref": "pkg:hex/cors_plug@3.0.0"}]},
{"id": "CVE-2024-1011", "description": "Prototype pollution in esbuild", "ratings": [{"severity": "HIGH"}], "affects": [{"ref": "pkg:hex/esbuild@0.14.0"}]},
{"id": "CVE-2024-1012", "description": "Log injection in Swoosh", "ratings": [{"severity": "INFO"}], "affects": [{"ref": "pkg:hex/swoosh@1.8.0"}]},
{"id": "CVE-2024-1013", "description": "Oban queue starvation", "ratings": [{"severity": "MEDIUM"}], "affects": [{"ref": "pkg:hex/oban@2.13.0"}]},
{"id": "CVE-2024-1014", "description": "Minor info leak", "ratings": [{"severity": "INFO"}], "affects": [{"ref": "pkg:hex/gettext@0.20.0"}]},
{"id": "CVE-2024-1015", "description": "Dependency confusion", "ratings": [{"severity": "HIGH"}], "affects": [{"ref": "pkg:hex/phoenix@1.7.0"}]}
{
"id": "CVE-2023-45857",
"description": "Axios Cross-Site Request Forgery vulnerability",
"detail": "An issue in Axios NPM package versions 0.8.1 through 1.5.1 allows a remote attacker to exploit XSRF token exposure in the HTTP header.",
"ratings": [{ "severity": "high", "score": 7.1, "method": "CVSSv31" }],
"advisories": [{ "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-45857" }],
"affects": [{ "ref": "axios@0.21.1" }]
},
{
"id": "CVE-2021-23337",
"description": "Lodash Command Injection via template function",
"detail": "Lodash versions prior to 4.17.21 are vulnerable to Command Injection via the template function allowing attackers to execute arbitrary commands.",
"ratings": [{ "severity": "critical", "score": 9.1, "method": "CVSSv31" }],
"advisories": [{ "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-23337" }],
"affects": [{ "ref": "lodash@4.17.20" }]
},
{
"id": "CVE-2022-31129",
"description": "Moment.js inefficient parsing causing denial of service",
"detail": "Affected versions of moment were found to use an inefficient parsing algorithm resulting in a Denial of Service for rfc2822 date strings.",
"ratings": [{ "severity": "high", "score": 7.5, "method": "CVSSv31" }],
"advisories": [{ "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-31129" }],
"affects": [{ "ref": "moment@2.29.3" }]
},
{
"id": "CVE-2023-26136",
"description": "tough-cookie Prototype Pollution",
"detail": "Versions of the package tough-cookie before 4.1.3 are vulnerable to Prototype Pollution due to improper handling of Cookies when using CookieJar in rejectPublicSuffixes=false mode.",
"ratings": [{ "severity": "medium", "score": 6.5, "method": "CVSSv31" }],
"advisories": [{ "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-26136" }],
"affects": [{ "ref": "axios@0.21.1" }]
},
{
"id": "CVE-2022-24999",
"description": "qs prototype poisoning vulnerability",
"detail": "qs before 6.10.3, as used in Express before 4.17.3, allows attackers to cause a Node process hang via a qs[__proto__][length] parameter.",
"ratings": [{ "severity": "high", "score": 7.5, "method": "CVSSv31" }],
"advisories": [{ "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24999" }],
"affects": [{ "ref": "webpack@5.75.0" }]
},
{
"id": "CVE-2022-23529",
"description": "jsonwebtoken insecure key retrieval",
"detail": "node-jsonwebtoken versions <= 8.5.1 are vulnerable to an insecure implementation of key retrieval function that could lead to Forgeable Public/Private Tokens.",
"ratings": [{ "severity": "high", "score": 7.6, "method": "CVSSv31" }],
"advisories": [{ "url": "https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-27h2-hvpr-p74q" }],
"affects": [{ "ref": "jsonwebtoken@8.5.1" }]
},
{
"id": "CVE-2023-44270",
"description": "PostCSS line return parsing error",
"detail": "An issue was discovered in PostCSS before 8.4.31. The vulnerability affects linters using PostCSS to parse external untrusted CSS, allowing an attacker to prepare malicious CSS.",
"ratings": [{ "severity": "medium", "score": 5.3, "method": "CVSSv31" }],
"advisories": [{ "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-44270" }],
"affects": [{ "ref": "postcss@8.4.21" }]
},
{
"id": "CVE-2023-26159",
"description": "follow-redirects URL redirect bypass",
"detail": "Versions of the package follow-redirects before 1.15.4 are vulnerable to Improper Input Validation due to the improper handling of URLs by the url.parse() function.",
"ratings": [{ "severity": "high", "score": 7.3, "method": "CVSSv31" }],
"advisories": [{ "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-26159" }],
"affects": [{ "ref": "follow-redirects@1.14.7" }]
},
{
"id": "CVE-2022-46175",
"description": "JSON5 Prototype Pollution",
"detail": "The parse method of the JSON5 library before version 2.2.2 does not restrict parsing of keys named __proto__, allowing attackers to alter prototype of Object.",
"ratings": [{ "severity": "high", "score": 7.1, "method": "CVSSv31" }],
"advisories": [{ "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-46175" }],
"affects": [{ "ref": "json5@2.2.1" }]
},
{
"id": "CVE-2022-24771",
"description": "node-forge signature verification lenient parsing",
"detail": "Forge prior to version 1.3.0, RSA PKCS#1 v1.5 signature verification code is lenient in checking the digest algorithm structure, allowing signature forgery.",
"ratings": [{ "severity": "high", "score": 7.5, "method": "CVSSv31" }],
"advisories": [{ "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24771" }],
"affects": [{ "ref": "node-forge@1.3.0" }]
},
{
"id": "CVE-2022-24772",
"description": "node-forge signature verification failure to check tailing garbage bytes",
"detail": "Forge prior to 1.3.0 RSA PKCS#1 v1.5 signature verification code does not check for tailing garbage bytes after decoding a DigestInfo ASN.1 structure.",
"ratings": [{ "severity": "critical", "score": 9.1, "method": "CVSSv31" }],
"advisories": [{ "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24772" }],
"affects": [{ "ref": "node-forge@1.3.0" }]
},
{
"id": "CVE-2023-0842",
"description": "xml2js prototype pollution",
"detail": "xml2js versions before 0.5.0 allows an external attacker to edit or add new properties to an object via prototype pollution.",
"ratings": [{ "severity": "medium", "score": 5.3, "method": "CVSSv31" }],
"advisories": [{ "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-0842" }],
"affects": [{ "ref": "webpack@5.75.0" }]
}
]
}
+33 -30
View File
@@ -1,6 +1,6 @@
{
"version": "2.1.0",
"$schema": "https://json.schemastore.org/sarif-2.1.0-rtm.5.json",
"version": "2.1.0",
"runs": [
{
"tool": {
@@ -8,41 +8,44 @@
"name": "gitleaks",
"version": "8.18.1",
"rules": [
{
"id": "generic-api-key",
"shortDescription": {
"text": "Generic API Key"
}
}
{ "id": "generic-api-key", "shortDescription": { "text": "Generic API Key" }, "helpUri": "https://github.com/gitleaks/gitleaks/blob/master/cmd/generate/config/rules/generic.go" },
{ "id": "aws-access-key-id", "shortDescription": { "text": "AWS Access Key ID" }, "helpUri": "https://github.com/gitleaks/gitleaks/blob/master/cmd/generate/config/rules/aws.go" },
{ "id": "private-key", "shortDescription": { "text": "Private Key" }, "helpUri": "https://github.com/gitleaks/gitleaks/blob/master/cmd/generate/config/rules/privatekey.go" },
{ "id": "github-pat", "shortDescription": { "text": "GitHub Personal Access Token" }, "helpUri": "https://github.com/gitleaks/gitleaks/blob/master/cmd/generate/config/rules/github.go" },
{ "id": "slack-webhook-url", "shortDescription": { "text": "Slack Webhook URL" }, "helpUri": "https://github.com/gitleaks/gitleaks/blob/master/cmd/generate/config/rules/slack.go" }
]
}
},
"results": [
{
"message": {
"text": "Generic API Key detected"
},
"ruleId": "generic-api-key",
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": ".env"
},
"region": {
"startLine": 1,
"startColumn": 11,
"endColumn": 43,
"snippet": {
"text": "API_KEY=SG.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}
],
"partialFingerprints": {
"primaryLocationLineHash": "a1b2c3d4e5f6g7h8"
}
"level": "error",
"message": { "text": "Generic API Key detected in environment file" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": ".env" }, "region": { "startLine": 3, "startColumn": 16, "endColumn": 48 } } }]
},
{
"ruleId": "aws-access-key-id",
"level": "error",
"message": { "text": "AWS Access Key ID found in configuration" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "config/deploy.py" }, "region": { "startLine": 14, "startColumn": 22, "endColumn": 42 } } }]
},
{
"ruleId": "private-key",
"level": "error",
"message": { "text": "Private key detected in source tree" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "certs/server.key" }, "region": { "startLine": 1, "startColumn": 1, "endColumn": 32 } } }]
},
{
"ruleId": "github-pat",
"level": "error",
"message": { "text": "GitHub Personal Access Token found in CI config" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": ".github/workflows/deploy.yml" }, "region": { "startLine": 28, "startColumn": 18, "endColumn": 58 } } }]
},
{
"ruleId": "slack-webhook-url",
"level": "warning",
"message": { "text": "Slack Webhook URL detected in notification handler" },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "scripts/notify.js" }, "region": { "startLine": 7, "startColumn": 24, "endColumn": 96 } } }]
}
]
}
+169
View File
@@ -0,0 +1,169 @@
{
"matches": [
{
"vulnerability": {
"id": "CVE-2023-44487",
"dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2023-44487",
"severity": "High",
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2023-44487"],
"description": "HTTP/2 Rapid Reset Attack",
"fix": { "versions": ["1.57.0"], "state": "fixed" }
},
"relatedVulnerabilities": [
{ "id": "CVE-2023-44487", "description": "The HTTP/2 protocol allows a denial of service because request cancellation can reset many streams quickly." }
],
"artifact": { "name": "golang.org/x/net", "version": "0.7.0", "type": "go-module", "language": "go" }
},
{
"vulnerability": {
"id": "CVE-2023-38545",
"dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2023-38545",
"severity": "Critical",
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2023-38545"],
"description": "SOCKS5 heap buffer overflow in curl",
"fix": { "versions": ["8.4.0"], "state": "fixed" }
},
"relatedVulnerabilities": [
{ "id": "CVE-2023-38545", "description": "This flaw makes curl overflow a heap based buffer in the SOCKS5 proxy handshake." }
],
"artifact": { "name": "curl", "version": "7.88.1-r1", "type": "apk", "language": "" }
},
{
"vulnerability": {
"id": "CVE-2024-21626",
"dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2024-21626",
"severity": "Critical",
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2024-21626"],
"description": "runc container breakout through process.cwd trickery",
"fix": { "versions": ["1.1.12"], "state": "fixed" }
},
"relatedVulnerabilities": [],
"artifact": { "name": "runc", "version": "1.1.5", "type": "apk", "language": "" }
},
{
"vulnerability": {
"id": "CVE-2023-5678",
"dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2023-5678",
"severity": "Medium",
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2023-5678"],
"description": "Excessive time spent in DH check / generation with large Q parameter value",
"fix": { "versions": ["3.1.5"], "state": "fixed" }
},
"relatedVulnerabilities": [],
"artifact": { "name": "openssl", "version": "3.1.2-r0", "type": "apk", "language": "" }
},
{
"vulnerability": {
"id": "CVE-2024-0727",
"dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2024-0727",
"severity": "Medium",
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2024-0727"],
"description": "PKCS12 decoding crash due to NULL dereference",
"fix": { "versions": ["3.2.1"], "state": "fixed" }
},
"relatedVulnerabilities": [],
"artifact": { "name": "openssl", "version": "3.1.2-r0", "type": "apk", "language": "" }
},
{
"vulnerability": {
"id": "CVE-2023-39325",
"dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2023-39325",
"severity": "High",
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2023-39325"],
"description": "HTTP/2 rapid resets can cause excessive work in net/http",
"fix": { "versions": ["0.17.0"], "state": "fixed" }
},
"relatedVulnerabilities": [],
"artifact": { "name": "golang.org/x/net", "version": "0.7.0", "type": "go-module", "language": "go" }
},
{
"vulnerability": {
"id": "CVE-2023-45283",
"dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2023-45283",
"severity": "High",
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2023-45283"],
"description": "Insecure parsing of Windows paths with a \\??\\ prefix in path/filepath",
"fix": { "versions": ["1.21.5"], "state": "fixed" }
},
"relatedVulnerabilities": [],
"artifact": { "name": "stdlib", "version": "1.21.3", "type": "go-module", "language": "go" }
},
{
"vulnerability": {
"id": "CVE-2023-47108",
"dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2023-47108",
"severity": "High",
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2023-47108"],
"description": "DoS vulnerability in otelgrpc due to unbound metrics cardinality",
"fix": { "versions": ["0.46.0"], "state": "fixed" }
},
"relatedVulnerabilities": [],
"artifact": { "name": "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc", "version": "0.42.0", "type": "go-module", "language": "go" }
},
{
"vulnerability": {
"id": "CVE-2024-24786",
"dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2024-24786",
"severity": "Medium",
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2024-24786"],
"description": "Infinite loop in JSON unmarshaling in google.golang.org/protobuf",
"fix": { "versions": ["1.33.0"], "state": "fixed" }
},
"relatedVulnerabilities": [],
"artifact": { "name": "google.golang.org/protobuf", "version": "1.31.0", "type": "go-module", "language": "go" }
},
{
"vulnerability": {
"id": "CVE-2023-45288",
"dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2023-45288",
"severity": "High",
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2023-45288"],
"description": "HTTP/2 CONTINUATION flood in net/http",
"fix": { "versions": ["1.22.2"], "state": "fixed" }
},
"relatedVulnerabilities": [],
"artifact": { "name": "stdlib", "version": "1.21.3", "type": "go-module", "language": "go" }
},
{
"vulnerability": {
"id": "CVE-2023-44488",
"dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2023-44488",
"severity": "Low",
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2023-44488"],
"description": "VP9 encoding crash in libvpx",
"fix": { "versions": ["1.13.1"], "state": "fixed" }
},
"relatedVulnerabilities": [],
"artifact": { "name": "libvpx", "version": "1.13.0-r0", "type": "apk", "language": "" }
},
{
"vulnerability": {
"id": "CVE-2023-6129",
"dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2023-6129",
"severity": "Medium",
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2023-6129"],
"description": "POLY1305 MAC on PowerPC corrupts vector registers",
"fix": { "versions": ["3.2.1"], "state": "fixed" }
},
"relatedVulnerabilities": [],
"artifact": { "name": "openssl", "version": "3.1.2-r0", "type": "apk", "language": "" }
}
],
"source": {
"type": "image",
"target": {
"userInput": "myorg/payment-service:v1.8.3",
"imageID": "sha256:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
"manifestDigest": "sha256:b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3"
}
},
"distro": {
"name": "alpine",
"version": "3.18.4",
"idLike": []
},
"descriptor": {
"name": "grype",
"version": "0.74.0"
}
}
+169 -32
View File
@@ -1,46 +1,183 @@
{
"$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json",
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "Semgrep",
"name": "semgrep",
"semanticVersion": "1.56.0",
"informationUri": "https://semgrep.dev",
"rules": [
{"id": "sql-injection", "shortDescription": {"text": "SQL Injection"}},
{"id": "xss", "shortDescription": {"text": "Cross-Site Scripting"}},
{"id": "hardcoded-secret", "shortDescription": {"text": "Hardcoded Secret"}},
{"id": "insecure-crypto", "shortDescription": {"text": "Insecure Crypto"}},
{"id": "missing-auth", "shortDescription": {"text": "Missing Authentication"}},
{"id": "broken-access-control", "shortDescription": {"text": "Broken Access Control"}},
{"id": "unsafe-deserialization", "shortDescription": {"text": "Unsafe Deserialization"}},
{"id": "path-traversal", "shortDescription": {"text": "Path Traversal"}},
{"id": "ssrf", "shortDescription": {"text": "SSRF"}},
{"id": "open-redirect", "shortDescription": {"text": "Open Redirect"}}
{
"id": "python.flask.security.xss.direct-use-of-request-data",
"shortDescription": { "text": "Direct use of request data in Flask response without escaping" },
"helpUri": "https://semgrep.dev/r/python.flask.security.xss.direct-use-of-request-data"
},
{
"id": "python.lang.security.audit.dangerous-system-call",
"shortDescription": { "text": "Dangerous system call with user-controlled input" },
"helpUri": "https://semgrep.dev/r/python.lang.security.audit.dangerous-system-call"
},
{
"id": "python.flask.security.injection.sql-injection",
"shortDescription": { "text": "SQL injection via string concatenation in Flask route" },
"helpUri": "https://semgrep.dev/r/python.flask.security.injection.sql-injection"
},
{
"id": "python.lang.security.audit.exec-detected",
"shortDescription": { "text": "Use of exec() detected" },
"helpUri": "https://semgrep.dev/r/python.lang.security.audit.exec-detected"
},
{
"id": "python.flask.security.open-redirect",
"shortDescription": { "text": "Open redirect vulnerability in Flask application" },
"helpUri": "https://semgrep.dev/r/python.flask.security.open-redirect"
},
{
"id": "python.lang.security.audit.insecure-hash-md5",
"shortDescription": { "text": "Use of insecure MD5 hash function" },
"helpUri": "https://semgrep.dev/r/python.lang.security.audit.insecure-hash-md5"
},
{
"id": "python.flask.security.audit.hardcoded-secret-key",
"shortDescription": { "text": "Hardcoded secret key in Flask application configuration" },
"helpUri": "https://semgrep.dev/r/python.flask.security.audit.hardcoded-secret-key"
},
{
"id": "python.lang.security.deserialization.avoid-pickle",
"shortDescription": { "text": "Avoid using pickle for deserialization of untrusted data" },
"helpUri": "https://semgrep.dev/r/python.lang.security.deserialization.avoid-pickle"
},
{
"id": "python.flask.security.audit.debug-enabled",
"shortDescription": { "text": "Flask application running with debug mode enabled" },
"helpUri": "https://semgrep.dev/r/python.flask.security.audit.debug-enabled"
},
{
"id": "python.lang.security.audit.dangerous-subprocess-use",
"shortDescription": { "text": "subprocess call with shell=True and user input" },
"helpUri": "https://semgrep.dev/r/python.lang.security.audit.dangerous-subprocess-use"
},
{
"id": "python.flask.security.injection.ssrf-requests",
"shortDescription": { "text": "Server-side request forgery via user-controlled URL" },
"helpUri": "https://semgrep.dev/r/python.flask.security.injection.ssrf-requests"
},
{
"id": "python.flask.security.audit.missing-jwt-verification",
"shortDescription": { "text": "JWT token decoded without signature verification" },
"helpUri": "https://semgrep.dev/r/python.flask.security.audit.missing-jwt-verification"
},
{
"id": "python.lang.security.audit.insecure-transport.requests-http",
"shortDescription": { "text": "HTTP request made without TLS" },
"helpUri": "https://semgrep.dev/r/python.lang.security.audit.insecure-transport.requests-http"
},
{
"id": "python.flask.security.injection.path-traversal",
"shortDescription": { "text": "Path traversal via user-controlled file path" },
"helpUri": "https://semgrep.dev/r/python.flask.security.injection.path-traversal"
},
{
"id": "python.lang.security.audit.logging-sensitive-data",
"shortDescription": { "text": "Sensitive data written to application logs" },
"helpUri": "https://semgrep.dev/r/python.lang.security.audit.logging-sensitive-data"
}
]
}
},
"results": [
{"ruleId": "sql-injection", "level": "error", "message": {"text": "SQL injection in user search"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/user_controller.ex"}, "region": {"startLine": 45}}}]},
{"ruleId": "sql-injection", "level": "error", "message": {"text": "SQL injection in reporting"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/report_worker.ex"}, "region": {"startLine": 112}}}]},
{"ruleId": "hardcoded-secret", "level": "error", "message": {"text": "AWS Key found"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "config/prod.exs"}, "region": {"startLine": 8}}}]},
{"ruleId": "xss", "level": "warning", "message": {"text": "Unescaped output in template"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/web/templates/page.html.heex"}, "region": {"startLine": 12}}}]},
{"ruleId": "xss", "level": "warning", "message": {"text": "Unescaped output in dashboard"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/web/templates/dashboard.html.heex"}, "region": {"startLine": 89}}}]},
{"ruleId": "insecure-crypto", "level": "warning", "message": {"text": "MD5 used for password hashing"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/auth/legacy_crypto.ex"}, "region": {"startLine": 23}}}]},
{"ruleId": "missing-auth", "level": "error", "message": {"text": "API endpoint /admin/export lacks auth"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/router.ex"}, "region": {"startLine": 156}}}]},
{"ruleId": "path-traversal", "level": "error", "message": {"text": "Unsafe file read"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/file_server.ex"}, "region": {"startLine": 34}}}]},
{"ruleId": "ssrf", "level": "warning", "message": {"text": "Webhook URL not validated"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/webhook_client.ex"}, "region": {"startLine": 67}}}]},
{"ruleId": "open-redirect", "level": "note", "message": {"text": "Redirect target from query param"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/auth_controller.ex"}, "region": {"startLine": 12}}}]},
{"ruleId": "sql-injection", "level": "error", "message": {"text": "SQL injection in legacy module"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/legacy/db.ex"}, "region": {"startLine": 201}}}]},
{"ruleId": "sql-injection", "level": "error", "message": {"text": "SQL injection in audit log"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/audit_logger.ex"}, "region": {"startLine": 55}}}]},
{"ruleId": "hardcoded-secret", "level": "error", "message": {"text": "Stripe secret found"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/billing.ex"}, "region": {"startLine": 10}}}]},
{"ruleId": "hardcoded-secret", "level": "error", "message": {"text": "SendGrid key found"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/mailer.ex"}, "region": {"startLine": 5}}}]},
{"ruleId": "xss", "level": "warning", "message": {"text": "Reflected XSS in search params"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/web/views/search_view.ex"}, "region": {"startLine": 32}}}]},
{"ruleId": "insecure-crypto", "level": "warning", "message": {"text": "Weak RSA key size"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/cert_manager.ex"}, "region": {"startLine": 144}}}]},
{"ruleId": "unsafe-deserialization", "level": "error", "message": {"text": "Erlang binary_to_term/1 on user input"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/session_store.ex"}, "region": {"startLine": 78}}}]},
{"ruleId": "broken-access-control", "level": "error", "message": {"text": "IDOR in profile update"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/web/controllers/user_controller.ex"}, "region": {"startLine": 210}}}]},
{"ruleId": "broken-access-control", "level": "error", "message": {"text": "IDOR in order history"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/web/controllers/order_controller.ex"}, "region": {"startLine": 45}}}]},
{"ruleId": "ssrf", "level": "warning", "message": {"text": "Metadata service access"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "lib/infra/metadata.ex"}, "region": {"startLine": 22}}}]}
{
"ruleId": "python.flask.security.xss.direct-use-of-request-data",
"level": "error",
"message": { "text": "User-controlled request data is passed directly to make_response() without escaping, which may allow cross-site scripting (XSS)." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/views/dashboard.py" }, "region": { "startLine": 47, "startColumn": 12, "endLine": 47, "endColumn": 58 } } }]
},
{
"ruleId": "python.lang.security.audit.dangerous-system-call",
"level": "error",
"message": { "text": "Found os.system() call with user-controlled input. Use subprocess.run() with shell=False and a list of arguments instead." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/utils/export.py" }, "region": { "startLine": 82, "startColumn": 4, "endLine": 82, "endColumn": 41 } } }]
},
{
"ruleId": "python.flask.security.injection.sql-injection",
"level": "error",
"message": { "text": "String concatenation used in SQL query. Use parameterized queries or an ORM to prevent SQL injection." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/models/user.py" }, "region": { "startLine": 134, "startColumn": 8, "endLine": 136, "endColumn": 5 } } }]
},
{
"ruleId": "python.lang.security.audit.exec-detected",
"level": "error",
"message": { "text": "Detected use of exec(). This can be dangerous if used with untrusted input, as it allows arbitrary code execution." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/plugins/loader.py" }, "region": { "startLine": 23, "startColumn": 8, "endLine": 23, "endColumn": 34 } } }]
},
{
"ruleId": "python.flask.security.open-redirect",
"level": "error",
"message": { "text": "User-controlled URL used in redirect() without validation. This can be exploited for phishing attacks." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/views/auth.py" }, "region": { "startLine": 56, "startColumn": 12, "endLine": 56, "endColumn": 48 } } }]
},
{
"ruleId": "python.lang.security.audit.insecure-hash-md5",
"level": "warning",
"message": { "text": "MD5 is a broken hash algorithm. Use hashlib.sha256() or stronger for security-sensitive operations." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/utils/crypto.py" }, "region": { "startLine": 15, "startColumn": 4, "endLine": 15, "endColumn": 35 } } }]
},
{
"ruleId": "python.flask.security.audit.hardcoded-secret-key",
"level": "error",
"message": { "text": "Hardcoded secret key found in Flask application configuration. Use environment variables instead." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/config.py" }, "region": { "startLine": 8, "startColumn": 1, "endLine": 8, "endColumn": 52 } } }]
},
{
"ruleId": "python.lang.security.deserialization.avoid-pickle",
"level": "error",
"message": { "text": "Detected use of pickle.loads() with potentially untrusted data. Pickle deserialization can lead to arbitrary code execution." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/cache/store.py" }, "region": { "startLine": 44, "startColumn": 16, "endLine": 44, "endColumn": 42 } } }]
},
{
"ruleId": "python.flask.security.audit.debug-enabled",
"level": "warning",
"message": { "text": "Flask debug mode is enabled. This exposes the interactive debugger and should never be used in production." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/__init__.py" }, "region": { "startLine": 31, "startColumn": 1, "endLine": 31, "endColumn": 22 } } }]
},
{
"ruleId": "python.lang.security.audit.dangerous-subprocess-use",
"level": "error",
"message": { "text": "subprocess.Popen called with shell=True and user-controlled arguments. This is vulnerable to shell injection." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/utils/export.py" }, "region": { "startLine": 95, "startColumn": 8, "endLine": 95, "endColumn": 72 } } }]
},
{
"ruleId": "python.flask.security.injection.ssrf-requests",
"level": "error",
"message": { "text": "User-controlled URL passed to requests.get(). Validate and restrict the URL to prevent server-side request forgery." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/views/proxy.py" }, "region": { "startLine": 18, "startColumn": 12, "endLine": 18, "endColumn": 55 } } }]
},
{
"ruleId": "python.flask.security.audit.missing-jwt-verification",
"level": "warning",
"message": { "text": "JWT token is decoded with verify=False. Always verify the signature to prevent token forgery." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/auth/middleware.py" }, "region": { "startLine": 27, "startColumn": 8, "endLine": 27, "endColumn": 62 } } }]
},
{
"ruleId": "python.lang.security.audit.insecure-transport.requests-http",
"level": "warning",
"message": { "text": "Requests call made using http:// instead of https://. Use TLS for all external communication." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/integrations/webhook.py" }, "region": { "startLine": 41, "startColumn": 12, "endLine": 41, "endColumn": 63 } } }]
},
{
"ruleId": "python.flask.security.injection.path-traversal",
"level": "error",
"message": { "text": "User-controlled file path passed to open() without sanitization. Validate path to prevent directory traversal." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/views/files.py" }, "region": { "startLine": 33, "startColumn": 8, "endLine": 33, "endColumn": 52 } } }]
},
{
"ruleId": "python.lang.security.audit.logging-sensitive-data",
"level": "note",
"message": { "text": "Password or token value is being written to application logs. Remove sensitive data from log statements." },
"locations": [{ "physicalLocation": { "artifactLocation": { "uri": "app/auth/login.py" }, "region": { "startLine": 61, "startColumn": 4, "endLine": 61, "endColumn": 48 } } }]
}
]
}
]
+152
View File
@@ -0,0 +1,152 @@
{
"ok": false,
"vulnerabilities": [
{
"id": "SNYK-JS-JSONWEBTOKEN-3180022",
"title": "Improper Restriction of Security Token Assignment",
"severity": "high",
"packageName": "jsonwebtoken",
"moduleName": "jsonwebtoken",
"version": "8.5.1",
"description": "jsonwebtoken is vulnerable to algorithm confusion attacks when using asymmetric signing.",
"identifiers": { "CVE": ["CVE-2022-23529"], "CWE": ["CWE-1259"] },
"references": [{ "url": "https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-27h2-hvpr-p74q" }],
"from": ["backend-api@1.0.0", "jsonwebtoken@8.5.1"],
"upgradePath": [false, "jsonwebtoken@9.0.0"],
"fixedIn": ["9.0.0"]
},
{
"id": "SNYK-JS-AXIOS-6032459",
"title": "Server-Side Request Forgery (SSRF)",
"severity": "high",
"packageName": "axios",
"moduleName": "axios",
"version": "0.21.4",
"description": "axios is vulnerable to SSRF due to improper protocol parsing in URLs.",
"identifiers": { "CVE": ["CVE-2023-45857"], "CWE": ["CWE-918"] },
"references": [{ "url": "https://github.com/axios/axios/security/advisories/GHSA-wf5p-g6vw-rhqq" }],
"from": ["backend-api@1.0.0", "axios@0.21.4"],
"upgradePath": [false, "axios@1.6.0"],
"fixedIn": ["1.6.0"]
},
{
"id": "SNYK-JS-SEMVER-3247795",
"title": "Regular Expression Denial of Service (ReDoS)",
"severity": "medium",
"packageName": "semver",
"moduleName": "semver",
"version": "5.7.1",
"description": "semver is vulnerable to ReDoS via the new Range function.",
"identifiers": { "CVE": ["CVE-2022-25883"], "CWE": ["CWE-1333"] },
"references": [{ "url": "https://github.com/npm/node-semver/pull/564" }],
"from": ["backend-api@1.0.0", "jest@29.5.0", "semver@5.7.1"],
"upgradePath": [],
"fixedIn": ["7.5.2"]
},
{
"id": "SNYK-JS-TOUGHCOOKIE-5672873",
"title": "Prototype Pollution",
"severity": "medium",
"packageName": "tough-cookie",
"moduleName": "tough-cookie",
"version": "4.1.2",
"description": "tough-cookie is vulnerable to Prototype Pollution via the rejectPublicSuffixes option.",
"identifiers": { "CVE": ["CVE-2023-26136"], "CWE": ["CWE-1321"] },
"references": [{ "url": "https://github.com/salesforce/tough-cookie/issues/282" }],
"from": ["backend-api@1.0.0", "jsdom@22.1.0", "tough-cookie@4.1.2"],
"upgradePath": [],
"fixedIn": ["4.1.3"]
},
{
"id": "SNYK-JS-XMLDOM-5414064",
"title": "Misinterpretation of Malicious XML Input",
"severity": "critical",
"packageName": "@xmldom/xmldom",
"moduleName": "@xmldom/xmldom",
"version": "0.8.6",
"description": "xmldom is vulnerable to accepting unclosed HTML entities which could lead to entity expansion attacks.",
"identifiers": { "CVE": ["CVE-2023-30616"], "CWE": ["CWE-611"] },
"references": [{ "url": "https://github.com/xmldom/xmldom/security/advisories/GHSA-crh6-fp67-6883" }],
"from": ["backend-api@1.0.0", "saml2-js@4.0.0", "@xmldom/xmldom@0.8.6"],
"upgradePath": [],
"fixedIn": ["0.8.8"]
},
{
"id": "SNYK-JS-MINIMATCH-3050818",
"title": "Regular Expression Denial of Service (ReDoS)",
"severity": "high",
"packageName": "minimatch",
"moduleName": "minimatch",
"version": "3.0.4",
"description": "minimatch is vulnerable to ReDoS when pattern contains braces.",
"identifiers": { "CVE": ["CVE-2022-3517"], "CWE": ["CWE-1333"] },
"references": [{ "url": "https://github.com/isaacs/minimatch/issues/97" }],
"from": ["backend-api@1.0.0", "glob@7.2.3", "minimatch@3.0.4"],
"upgradePath": [],
"fixedIn": ["3.0.5"]
},
{
"id": "SNYK-JS-WORD-WRAP-3149973",
"title": "Regular Expression Denial of Service (ReDoS)",
"severity": "low",
"packageName": "word-wrap",
"moduleName": "word-wrap",
"version": "1.2.3",
"description": "word-wrap is vulnerable to ReDoS due to inefficient regex in the wrap function.",
"identifiers": { "CVE": ["CVE-2023-26115"], "CWE": ["CWE-1333"] },
"references": [{ "url": "https://github.com/jonschlinkert/word-wrap/issues/33" }],
"from": ["backend-api@1.0.0", "eslint@8.44.0", "optionator@0.9.3", "word-wrap@1.2.3"],
"upgradePath": [],
"fixedIn": ["1.2.4"]
},
{
"id": "SNYK-JS-IP-6240174",
"title": "Server-Side Request Forgery (SSRF)",
"severity": "high",
"packageName": "ip",
"moduleName": "ip",
"version": "2.0.0",
"description": "ip is vulnerable to SSRF because isPublic and isPrivate do not properly check IPv6 addresses.",
"identifiers": { "CVE": ["CVE-2024-29415"], "CWE": ["CWE-918"] },
"references": [{ "url": "https://github.com/indutny/node-ip/issues/150" }],
"from": ["backend-api@1.0.0", "pac-proxy-agent@7.0.1", "ip@2.0.0"],
"upgradePath": [],
"fixedIn": ["2.0.1"]
},
{
"id": "SNYK-JS-FOLLOWREDIRECTS-6444610",
"title": "Exposure of Sensitive Information to an Unauthorized Actor",
"severity": "medium",
"packageName": "follow-redirects",
"moduleName": "follow-redirects",
"version": "1.15.3",
"description": "follow-redirects leaks the proxy-authentication header to third-party hostnames on redirect.",
"identifiers": { "CVE": ["CVE-2024-28849"], "CWE": ["CWE-200"] },
"references": [{ "url": "https://github.com/follow-redirects/follow-redirects/security/advisories/GHSA-cxjh-pqwp-8mfp" }],
"from": ["backend-api@1.0.0", "axios@0.21.4", "follow-redirects@1.15.3"],
"upgradePath": [],
"fixedIn": ["1.15.6"]
},
{
"id": "SNYK-JS-EXPRESS-6474509",
"title": "Improper Input Validation",
"severity": "medium",
"packageName": "express",
"moduleName": "express",
"version": "4.18.2",
"description": "express is vulnerable to Open Redirect via URL validation bypass in res.location.",
"identifiers": { "CVE": ["CVE-2024-29041"], "CWE": ["CWE-20"] },
"references": [{ "url": "https://github.com/expressjs/express/security/advisories/GHSA-rv95-896h-c2vc" }],
"from": ["backend-api@1.0.0", "express@4.18.2"],
"upgradePath": [false, "express@4.19.2"],
"fixedIn": ["4.19.2"]
}
],
"projectName": "myorg/backend-api",
"packageManager": "npm",
"summary": "10 vulnerable dependency paths",
"dependencyCount": 487,
"org": "myorg",
"licensesPolicy": null,
"path": "/app"
}
+116 -16
View File
@@ -2,26 +2,126 @@
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "sec-dashboard",
"documentNamespace": "http://spdx.org/spdxdocs/sec-dashboard-123",
"name": "api-server-sbom",
"documentNamespace": "https://spdx.org/spdxdocs/api-server-sbom-v2.4.1",
"creationInfo": {
"creators": [
"Tool: spdx-bom-generator"
],
"created": "2026-05-13T12:00:00Z"
"created": "2026-05-10T08:30:00Z",
"creators": ["Tool: syft-0.105.0", "Organization: myorg"]
},
"packages": [
{
"name": "phoenix",
"SPDXID": "SPDXRef-Package-phoenix",
"versionInfo": "1.7.10",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:hex/phoenix@1.7.10"
}
]
"SPDXID": "SPDXRef-Package-flask",
"name": "flask",
"versionInfo": "3.0.0",
"downloadLocation": "https://pypi.org/project/Flask/3.0.0/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/flask@3.0.0" }]
},
{
"SPDXID": "SPDXRef-Package-werkzeug",
"name": "werkzeug",
"versionInfo": "3.0.1",
"downloadLocation": "https://pypi.org/project/Werkzeug/3.0.1/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/werkzeug@3.0.1" }]
},
{
"SPDXID": "SPDXRef-Package-jinja2",
"name": "jinja2",
"versionInfo": "3.1.2",
"downloadLocation": "https://pypi.org/project/Jinja2/3.1.2/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/jinja2@3.1.2" }]
},
{
"SPDXID": "SPDXRef-Package-requests",
"name": "requests",
"versionInfo": "2.31.0",
"downloadLocation": "https://pypi.org/project/requests/2.31.0/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/requests@2.31.0" }]
},
{
"SPDXID": "SPDXRef-Package-urllib3",
"name": "urllib3",
"versionInfo": "2.1.0",
"downloadLocation": "https://pypi.org/project/urllib3/2.1.0/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/urllib3@2.1.0" }]
},
{
"SPDXID": "SPDXRef-Package-sqlalchemy",
"name": "sqlalchemy",
"versionInfo": "2.0.23",
"downloadLocation": "https://pypi.org/project/SQLAlchemy/2.0.23/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/sqlalchemy@2.0.23" }]
},
{
"SPDXID": "SPDXRef-Package-pydantic",
"name": "pydantic",
"versionInfo": "2.5.2",
"downloadLocation": "https://pypi.org/project/pydantic/2.5.2/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/pydantic@2.5.2" }]
},
{
"SPDXID": "SPDXRef-Package-celery",
"name": "celery",
"versionInfo": "5.3.6",
"downloadLocation": "https://pypi.org/project/celery/5.3.6/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/celery@5.3.6" }]
},
{
"SPDXID": "SPDXRef-Package-redis",
"name": "redis",
"versionInfo": "5.0.1",
"downloadLocation": "https://pypi.org/project/redis/5.0.1/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/redis@5.0.1" }]
},
{
"SPDXID": "SPDXRef-Package-cryptography",
"name": "cryptography",
"versionInfo": "41.0.7",
"downloadLocation": "https://pypi.org/project/cryptography/41.0.7/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/cryptography@41.0.7" }]
},
{
"SPDXID": "SPDXRef-Package-gunicorn",
"name": "gunicorn",
"versionInfo": "21.2.0",
"downloadLocation": "https://pypi.org/project/gunicorn/21.2.0/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/gunicorn@21.2.0" }]
},
{
"SPDXID": "SPDXRef-Package-psycopg2",
"name": "psycopg2-binary",
"versionInfo": "2.9.9",
"downloadLocation": "https://pypi.org/project/psycopg2-binary/2.9.9/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/psycopg2-binary@2.9.9" }]
},
{
"SPDXID": "SPDXRef-Package-markupsafe",
"name": "markupsafe",
"versionInfo": "2.1.3",
"downloadLocation": "https://pypi.org/project/MarkupSafe/2.1.3/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/markupsafe@2.1.3" }]
},
{
"SPDXID": "SPDXRef-Package-click",
"name": "click",
"versionInfo": "8.1.7",
"downloadLocation": "https://pypi.org/project/click/8.1.7/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/click@8.1.7" }]
},
{
"SPDXID": "SPDXRef-Package-certifi",
"name": "certifi",
"versionInfo": "2023.11.17",
"downloadLocation": "https://pypi.org/project/certifi/2023.11.17/",
"externalRefs": [{ "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", "referenceLocator": "pkg:pypi/certifi@2023.11.17" }]
}
],
"relationships": [
{ "spdxElementId": "SPDXRef-DOCUMENT", "relatedSpdxElement": "SPDXRef-Package-flask", "relationshipType": "DESCRIBES" },
{ "spdxElementId": "SPDXRef-Package-flask", "relatedSpdxElement": "SPDXRef-Package-werkzeug", "relationshipType": "DEPENDS_ON" },
{ "spdxElementId": "SPDXRef-Package-flask", "relatedSpdxElement": "SPDXRef-Package-jinja2", "relationshipType": "DEPENDS_ON" },
{ "spdxElementId": "SPDXRef-Package-flask", "relatedSpdxElement": "SPDXRef-Package-click", "relationshipType": "DEPENDS_ON" },
{ "spdxElementId": "SPDXRef-Package-flask", "relatedSpdxElement": "SPDXRef-Package-markupsafe", "relationshipType": "DEPENDS_ON" },
{ "spdxElementId": "SPDXRef-Package-requests", "relatedSpdxElement": "SPDXRef-Package-urllib3", "relationshipType": "DEPENDS_ON" },
{ "spdxElementId": "SPDXRef-Package-requests", "relatedSpdxElement": "SPDXRef-Package-certifi", "relationshipType": "DEPENDS_ON" }
]
}
+247 -39
View File
@@ -1,68 +1,276 @@
{
"SchemaVersion": 2,
"ArtifactName": "enterprise-monolith:v4.2.0-rc1",
"ArtifactName": "myorg/api-server",
"ArtifactType": "container_image",
"Metadata": {
"OS": {"Family": "debian", "Name": "11.7"},
"ImageConfig": {"architecture": "amd64"}
"OS": {
"Family": "alpine",
"Name": "3.18.6"
},
"ImageID": "sha256:a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
"RepoTags": ["myorg/api-server:v2.4.1"],
"ImageConfig": {
"architecture": "amd64",
"os": "linux"
}
},
"Results": [
{
"Target": "debian 11.7",
"Target": "myorg/api-server:v2.4.1 (alpine 3.18.6)",
"Class": "os-pkgs",
"Type": "alpine",
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2024-0001", "PkgName": "libc6", "Severity": "CRITICAL", "InstalledVersion": "2.31-13+deb11u6", "FixedVersion": "2.31-13+deb11u7",
"Title": "Buffer Overflow in glibc", "Description": "A critical flaw in glibc allows remote attackers to execute code via crafted DNS responses.",
"References": ["https://security-tracker.debian.org/tracker/CVE-2024-0001"]
"VulnerabilityID": "CVE-2023-44487",
"PkgName": "nghttp2-libs",
"InstalledVersion": "1.51.0-r0",
"FixedVersion": "1.57.0-r0",
"Severity": "CRITICAL",
"Title": "HTTP/2 Rapid Reset Attack",
"Description": "The HTTP/2 protocol allows a denial of service because request cancellation can reset many streams quickly, leading to excessive server resource consumption.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2023-44487",
"https://www.cve.org/CVERecord?id=CVE-2023-44487"
]
},
{
"VulnerabilityID": "CVE-2023-1234", "PkgName": "libssl1.1", "Severity": "HIGH", "InstalledVersion": "1.1.1n-0+deb11u4", "FixedVersion": "1.1.1n-0+deb11u5",
"Title": "Weak Handshake in OpenSSL", "Description": "OpenSSL is vulnerable to a handshake downgrade attack.",
"References": ["https://security-tracker.debian.org/tracker/CVE-2023-1234"]
"VulnerabilityID": "CVE-2023-38545",
"PkgName": "curl",
"InstalledVersion": "8.1.2-r0",
"FixedVersion": "8.4.0-r0",
"Severity": "CRITICAL",
"Title": "curl SOCKS5 heap buffer overflow",
"Description": "A heap-based buffer overflow vulnerability exists in curl's SOCKS5 proxy handshake. When curl is asked to pass along the hostname to the SOCKS5 proxy, the hostname buffer can overflow.",
"References": [
"https://curl.se/docs/CVE-2023-38545.html",
"https://nvd.nist.gov/vuln/detail/CVE-2023-38545"
]
},
{
"VulnerabilityID": "CVE-2023-5555", "PkgName": "bash", "Severity": "MEDIUM", "InstalledVersion": "5.1-2+deb11u1", "FixedVersion": "5.1-2+deb11u2",
"Title": "Logic error in path handling", "Description": "Bash path expansion contains a logic error allowing sandbox escape.",
"References": ["https://security-tracker.debian.org/tracker/CVE-2023-5555"]
"VulnerabilityID": "CVE-2024-21626",
"PkgName": "runc",
"InstalledVersion": "1.1.9-r0",
"FixedVersion": "1.1.12-r0",
"Severity": "CRITICAL",
"Title": "runc container breakout through process.cwd trickery and leaked fds",
"Description": "In runc 1.1.11 and earlier, due to an internal file descriptor leak, an attacker could cause a newly-spawned container process to have a working directory in the host filesystem namespace, allowing container escape.",
"References": [
"https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv"
]
},
{
"VulnerabilityID": "CVE-2023-0001", "PkgName": "zlib1g", "Severity": "LOW", "InstalledVersion": "1:1.2.11.dfsg-2+deb11u2", "FixedVersion": "1:1.2.11.dfsg-2+deb11u3",
"Title": "Memory leak in compression", "Description": "A minor memory leak in zlib when handling corrupted streams.",
"References": ["https://security-tracker.debian.org/tracker/CVE-2023-0001"]
"VulnerabilityID": "CVE-2023-52425",
"PkgName": "expat",
"InstalledVersion": "2.5.0-r1",
"FixedVersion": "2.6.0-r0",
"Severity": "HIGH",
"Title": "libexpat: parsing large tokens can trigger a denial of service",
"Description": "libexpat through 2.5.0 allows a denial of service via a crafted XML document with a large number of token characters in a single attribute value.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2023-52425"
]
},
{"VulnerabilityID": "CVE-2023-0002", "PkgName": "sed", "Severity": "LOW", "InstalledVersion": "4.8-1", "FixedVersion": "4.8-1+deb11u1"},
{"VulnerabilityID": "CVE-2023-0003", "PkgName": "grep", "Severity": "INFO", "InstalledVersion": "3.4-1", "FixedVersion": "3.4-1+deb11u1"},
{"VulnerabilityID": "CVE-2023-0004", "PkgName": "tar", "Severity": "INFO", "InstalledVersion": "1.34+dfsg-1", "FixedVersion": "1.34+dfsg-1+deb11u1"},
{"VulnerabilityID": "CVE-2023-0005", "PkgName": "gzip", "Severity": "INFO", "InstalledVersion": "1.10-4+deb11u1"},
{"VulnerabilityID": "CVE-2023-0006", "PkgName": "coreutils", "Severity": "INFO", "InstalledVersion": "8.32-4.1"},
{"VulnerabilityID": "CVE-2023-0007", "PkgName": "findutils", "Severity": "INFO", "InstalledVersion": "4.8.0-1"}
{
"VulnerabilityID": "CVE-2024-0727",
"PkgName": "libssl3",
"InstalledVersion": "3.1.3-r0",
"FixedVersion": "3.1.4-r3",
"Severity": "HIGH",
"Title": "openssl: denial of service via null dereference",
"Description": "Processing a maliciously formatted PKCS12 file may lead OpenSSL to crash leading to a potential denial of service attack.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2024-0727"
]
},
{
"VulnerabilityID": "CVE-2023-6129",
"PkgName": "libcrypto3",
"InstalledVersion": "3.1.3-r0",
"FixedVersion": "3.1.4-r2",
"Severity": "HIGH",
"Title": "openssl: POLY1305 MAC implementation corrupts vector registers on PowerPC",
"Description": "The POLY1305 MAC implementation in OpenSSL does not save the contents of non-volatile XMM registers on Windows 64 platform when calculating the MAC of data larger than 64 bytes.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2023-6129"
]
},
{
"VulnerabilityID": "CVE-2023-6237",
"PkgName": "libcrypto3",
"InstalledVersion": "3.1.3-r0",
"FixedVersion": "3.1.4-r4",
"Severity": "MEDIUM",
"Title": "openssl: Excessive time spent checking invalid RSA public keys",
"Description": "A flaw was found in OpenSSL. Checking excessively long invalid RSA public keys may take a long time, causing a denial of service.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2023-6237"
]
},
{
"VulnerabilityID": "CVE-2024-2511",
"PkgName": "libssl3",
"InstalledVersion": "3.1.3-r0",
"FixedVersion": "3.1.5-r0",
"Severity": "MEDIUM",
"Title": "openssl: Unbounded memory growth with session handling in TLSv1.3",
"Description": "Some non-default TLS server configurations can cause unbounded memory growth when processing TLSv1.3 sessions, leading to a denial of service.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2024-2511"
]
},
{
"VulnerabilityID": "CVE-2023-5678",
"PkgName": "libcrypto3",
"InstalledVersion": "3.1.3-r0",
"FixedVersion": "3.1.4-r1",
"Severity": "MEDIUM",
"Title": "openssl: Generating excessively long X9.42 DH keys may be very slow",
"Description": "Generating excessively long X9.42 DH keys or checking excessively long X9.42 DH keys or parameters may be very slow.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2023-5678"
]
},
{
"VulnerabilityID": "CVE-2023-3817",
"PkgName": "libcrypto3",
"InstalledVersion": "3.1.3-r0",
"FixedVersion": "3.1.4-r0",
"Severity": "LOW",
"Title": "openssl: Excessive time spent checking DH q parameter value",
"Description": "Checking excessively long DH keys or parameters may be very slow. Applications that use DH_check() may experience long delays.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2023-3817"
]
},
{
"VulnerabilityID": "CVE-2023-4807",
"PkgName": "libcrypto3",
"InstalledVersion": "3.1.3-r0",
"FixedVersion": "3.1.3-r1",
"Severity": "LOW",
"Title": "openssl: POLY1305 MAC implementation corrupts XMM registers on Windows",
"Description": "The POLY1305 MAC implementation in OpenSSL corrupts non-volatile XMM registers on Windows 64-bit platform.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2023-4807"
]
},
{
"VulnerabilityID": "CVE-2023-5363",
"PkgName": "libssl3",
"InstalledVersion": "3.1.3-r0",
"FixedVersion": "3.1.4-r0",
"Severity": "LOW",
"Title": "openssl: Incorrect cipher key and IV length processing",
"Description": "A bug has been identified in the processing of key and initialisation vector (IV) lengths which can lead to potential truncation or overruns during initialisation of some symmetric ciphers.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2023-5363"
]
}
]
},
{
"Target": "package-lock.json",
"Target": "Node.js",
"Class": "lang-pkgs",
"Type": "node-pkg",
"Vulnerabilities": [
{
"VulnerabilityID": "GHSA-pw56-c772-f47p", "PkgName": "lodash", "Severity": "HIGH", "InstalledVersion": "4.17.20", "FixedVersion": "4.17.21",
"Title": "Prototype Pollution in lodash", "Description": "Lodash versions prior to 4.17.21 are vulnerable to prototype pollution.",
"References": ["https://github.com/advisories/GHSA-pw56-c772-f47p"]
"VulnerabilityID": "CVE-2024-29041",
"PkgName": "express",
"InstalledVersion": "4.17.1",
"FixedVersion": "4.19.2",
"Severity": "MEDIUM",
"Title": "Express.js Open Redirect vulnerability",
"Description": "Versions of Express.js prior to 4.19.2 are vulnerable to open redirects due to improper validation of URLs in the res.location and res.redirect functions.",
"References": [
"https://github.com/expressjs/express/security/advisories/GHSA-rv95-896h-c2vc"
]
},
{
"VulnerabilityID": "GHSA-p8p7-6pxg-8p8p", "PkgName": "axios", "Severity": "MEDIUM", "InstalledVersion": "0.21.1", "FixedVersion": "1.6.0",
"Title": "SSRF in axios", "Description": "Axios before 1.6.0 is vulnerable to Server-Side Request Forgery.",
"References": ["https://github.com/advisories/GHSA-p8p7-6pxg-8p8p"]
"VulnerabilityID": "CVE-2021-23337",
"PkgName": "lodash",
"InstalledVersion": "4.17.20",
"FixedVersion": "4.17.21",
"Severity": "HIGH",
"Title": "lodash: command injection via template function",
"Description": "Lodash versions prior to 4.17.21 are vulnerable to Command Injection via the template function.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2021-23337",
"https://snyk.io/vuln/SNYK-JS-LODASH-1040724"
]
},
{"VulnerabilityID": "GHSA-mock-1", "PkgName": "moment", "Severity": "LOW", "InstalledVersion": "2.29.1", "FixedVersion": "2.29.4"},
{"VulnerabilityID": "GHSA-mock-2", "PkgName": "react", "Severity": "LOW", "InstalledVersion": "17.0.1", "FixedVersion": "17.0.2"},
{"VulnerabilityID": "GHSA-mock-3", "PkgName": "next", "Severity": "MEDIUM", "InstalledVersion": "11.0.0", "FixedVersion": "12.0.0"},
{"VulnerabilityID": "GHSA-mock-4", "PkgName": "express", "Severity": "MEDIUM", "InstalledVersion": "4.17.1", "FixedVersion": "4.17.3"},
{"VulnerabilityID": "GHSA-mock-5", "PkgName": "qs", "Severity": "MEDIUM", "InstalledVersion": "6.7.0", "FixedVersion": "6.7.3"},
{"VulnerabilityID": "GHSA-mock-6", "PkgName": "debug", "Severity": "LOW", "InstalledVersion": "4.1.1", "FixedVersion": "4.3.1"},
{"VulnerabilityID": "GHSA-mock-7", "PkgName": "semver", "Severity": "LOW", "InstalledVersion": "7.3.2", "FixedVersion": "7.5.2"},
{"VulnerabilityID": "GHSA-mock-8", "PkgName": "yargs", "Severity": "INFO", "InstalledVersion": "16.2.0"},
{"VulnerabilityID": "GHSA-mock-9", "PkgName": "chalk", "Severity": "INFO", "InstalledVersion": "4.1.0"},
{"VulnerabilityID": "GHSA-mock-10", "PkgName": "dotenv", "Severity": "INFO", "InstalledVersion": "8.2.0"}
{
"VulnerabilityID": "CVE-2021-44906",
"PkgName": "minimist",
"InstalledVersion": "1.2.5",
"FixedVersion": "1.2.6",
"Severity": "CRITICAL",
"Title": "minimist: prototype pollution",
"Description": "Minimist prior to 1.2.6 is vulnerable to Prototype Pollution via the main function.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2021-44906"
]
},
{
"VulnerabilityID": "CVE-2022-24999",
"PkgName": "qs",
"InstalledVersion": "6.7.0",
"FixedVersion": "6.7.3",
"Severity": "HIGH",
"Title": "qs: prototype poisoning vulnerability",
"Description": "qs before 6.10.3, as used in Express before 4.17.3, allows attackers to cause a Node process hang.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2022-24999"
]
},
{
"VulnerabilityID": "CVE-2022-23529",
"PkgName": "jsonwebtoken",
"InstalledVersion": "8.5.1",
"FixedVersion": "9.0.0",
"Severity": "HIGH",
"Title": "jsonwebtoken: Insecure Key Retrieval",
"Description": "node-jsonwebtoken up to 8.5.1 is vulnerable to an insecure key retrieval flaw that allows an attacker to craft JWTs that bypass authentication.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2022-23529",
"https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-27h2-hvpr-p74q"
]
},
{
"VulnerabilityID": "CVE-2023-26136",
"PkgName": "tough-cookie",
"InstalledVersion": "2.5.0",
"FixedVersion": "4.1.3",
"Severity": "MEDIUM",
"Title": "tough-cookie: prototype pollution",
"Description": "Versions of tough-cookie before 4.1.3 are vulnerable to Prototype Pollution due to improper handling of Cookies when using CookieJar.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2023-26136"
]
},
{
"VulnerabilityID": "CVE-2022-25883",
"PkgName": "semver",
"InstalledVersion": "5.7.1",
"FixedVersion": "5.7.2",
"Severity": "MEDIUM",
"Title": "semver: Regular Expression Denial of Service",
"Description": "Versions of the semver package before 7.5.2, 6.3.1, and 5.7.2 are vulnerable to Regular Expression Denial of Service via the function new Range.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2022-25883"
]
},
{
"VulnerabilityID": "CVE-2023-45133",
"PkgName": "traverse",
"InstalledVersion": "0.6.6",
"FixedVersion": "0.6.7",
"Severity": "LOW",
"Title": "babel traverse: code injection via crafted code",
"Description": "In Babel versions prior to 7.23.2, using Babel to compile specifically crafted code can lead to arbitrary code execution.",
"References": [
"https://nvd.nist.gov/vuln/detail/CVE-2023-45133"
]
}
]
}
]