Use when: analysing Trivy CVE scan results for the homelab, assessing vulnerability severity in context, determining which services are internet-exposed vs LAN-only, identifying unmanaged containers, or writing a CVE report for Discord. Covers the exposure model, Ansible-managed service inventory, and risk-assessment guidelines.
Scanned 9/1/2026
Install to Claude Code
npx -y skills add jimmybish/homelab --skill cve-scan-analysis --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Cve Scan Analysis?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jimmybish-cve-scan-analysis)More formats (shields.io, HTML) on the badges page.
---
name: cve-scan-analysis
description: 'Use when: analysing Trivy CVE scan results for the homelab, assessing vulnerability severity in context, determining which services are internet-exposed vs LAN-only, identifying unmanaged containers, or writing a CVE report for Discord. Covers the exposure model, Ansible-managed service inventory, and risk-assessment guidelines.'
---
# CVE Scan Analysis
How to interpret Trivy vulnerability scan results for the homelab infrastructure so you can prioritize the real risks, flag unmanaged containers, and produce actionable recommendations.
## When to Use
- Processing output from the weekly CVE Monitor n8n workflow
- A user asks for a CVE review or vulnerability assessment of the homelab
- Triaging whether a specific CVE is urgent for this environment
## Analysis Order
Work through findings in this order:
1. Match each image to a managed service or mark it as unmanaged.
2. Determine the service exposure level: internet-exposed, LAN-only, API-only, or supporting infrastructure.
3. Check CVE severity and whether a fix or known exploit exists.
4. Assign the finding to an urgency tier.
5. Write the report with only the highest-value findings and the relevant update command.
## Service Exposure Model
Not all services carry the same risk. A CRITICAL CVE in a LAN-only service is far less urgent than a HIGH in an internet-facing one. Use the categories below to calibrate urgency.
### Internet-Exposed Services
These services are reachable from the public internet and are the highest priority for patching.
| Service | Exposure Method | Notes |
|---------|----------------|-------|
| **Plex** | Direct port forward (TCP 32400) | Not behind reverse proxy — directly exposed to the internet on the host |
| **Jellyfin** | SWAG external reverse proxy | `external/nginx/proxy-confs/jellyfin.subdomain.conf` |
| **Jellyseerr** | SWAG external reverse proxy | `external/nginx/proxy-confs/jellyseerr.subdomain.conf` |
**Rule of thumb:** Any nginx proxy config deployed to `{{ proxy_folder }}/external/nginx/proxy-confs/` makes that service internet-accessible.
### LAN-Only Services (Internal Proxy)
These are accessible only from the local network via the internal SWAG instance. Exploitation requires an attacker to already be on the LAN or to chain with another vulnerability.
| Service | Proxy Config Path |
|---------|-------------------|
| Grafana | `internal/nginx/proxy-confs/grafana.subdomain.conf` |
| Homepage | `internal/nginx/proxy-confs/homepage.subdomain.conf` |
| n8n | `internal/nginx/proxy-confs/n8n.subdomain.conf` |
| Paperless-NGX | `internal/nginx/proxy-confs/paperless.subdomain.conf` |
| Tracearr | `internal/nginx/proxy-confs/tracearr.subdomain.conf` |
| Overseerr | `internal/nginx/proxy-confs/overseerr.subdomain.conf` |
| Maintainerr | `internal/nginx/proxy-confs/maintainerr.subdomain.conf` |
| Sonarr | `internal/nginx/proxy-confs/sonarr.subdomain.conf` |
| Radarr | `internal/nginx/proxy-confs/radarr.subdomain.conf` |
| Prowlarr | `internal/nginx/proxy-confs/prowlarr.subdomain.conf` |
| Lidarr | `internal/nginx/proxy-confs/lidarr.subdomain.conf` |
| SABnzbd | `internal/nginx/proxy-confs/sabnzbd.subdomain.conf` |
| qBittorrent | `internal/nginx/proxy-confs/qbittorrent.subdomain.conf` |
| Home Assistant | `internal/nginx/proxy-confs/homeassistant.subdomain.conf` |
| Frigate | `internal/nginx/proxy-confs/frigate.subdomain.conf` |
### No Web Interface / API-Only
These have no user-facing web UI and no reverse proxy. They are only reachable on their specific port from the LAN.
| Service | Notes |
|---------|-------|
| MCP Sonarr, MCP Radarr, MCP Tracearr | Agent-only REST APIs, no browser access |
| JellyPlex-Watched | Background sync daemon, no listening port |
| Alloy | Monitoring agent, pushes metrics/logs outbound |
### Supporting Infrastructure (Not Directly User-Facing)
These run as part of multi-container stacks and are typically not exposed outside their Docker network.
| Service | Part of Stack |
|---------|---------------|
| PostgreSQL | Paperless-NGX, Tracearr (TimescaleDB) |
| Redis | Paperless-NGX, Tracearr |
| Gotenberg | Paperless-NGX |
| Apache Tika | Paperless-NGX |
| Prometheus, Loki, InfluxDB, SNMP Exporter | Grafana stack |
CVEs in these are lower priority — they aren't directly reachable — but still worth noting if CRITICAL, since a compromised front-end service could pivot to them.
## Ansible-Managed Service Inventory
The following Docker images are deployed and managed by Ansible roles. **Any container running on a Docker host whose image is NOT in this list is unmanaged** — it was started manually or by another tool and should be called out in the report.
### Managed Images by Role
| Role | Images |
|------|--------|
| `arr_suite` | `lscr.io/linuxserver/sonarr`, `lscr.io/linuxserver/radarr`, `lscr.io/linuxserver/prowlarr`, `lscr.io/linuxserver/lidarr`, `lscr.io/linuxserver/sabnzbd`, `lscr.io/linuxserver/qbittorrent` |
| `grafana` | `grafana/grafana-oss`, `grafana/loki`, `prom/prometheus`, `influxdb`, `prom/snmp-exporter` |
| `homepage` | `ghcr.io/gethomepage/homepage` |
| `jellyfin` | `lscr.io/linuxserver/jellyfin` |
| `jellyplex_watched` | `luigi311/jellyplex-watched` |
| `jellyseerr` | `fallenbagel/jellyseerr` |
| `maintainerr` | `jorenn92/maintainerr` |
| `n8n` | `n8nio/n8n` |
| `overseerr` | `lscr.io/linuxserver/overseerr` |
| `paperless-ngx` | `ghcr.io/paperless-ngx/paperless-ngx`, `postgres`, `redis`, `gotenberg/gotenberg`, `apache/tika` |
| `plex` | `lscr.io/linuxserver/plex` |
| `proxy` | `linuxserver/swag` (×2 instances) |
| `tracearr` | `ghcr.io/connorgallopo/tracearr`, `timescale/timescaledb-ha`, `redis` |
| `docker` | `gcr.io/cadvisor/cadvisor` (deployed on all Docker hosts) |
| `mcp_sonarr` | `mcp-sonarr` (locally built) |
| `mcp_radarr` | `mcp-radarr` (locally built) |
| `mcp_tracearr` | `mcp-tracearr` (locally built) |
| `alloy` | Non-Docker (systemd service) |
When comparing scanned images against this list, normalize by stripping the registry prefix and tag — e.g. `docker.io/n8nio/n8n:latest` matches `n8nio/n8n`. Images prefixed `mcp-` are locally built and won't appear in Trivy scans (they can't be pulled from a registry).
## Risk Assessment Guidelines
### Urgency Tiers
Group findings into these tiers in the report:
**Upgrade Now** — Act this week:
- Any CRITICAL or HIGH CVE in an **internet-exposed** service (Plex, Jellyfin, Jellyseerr)
- Any CRITICAL with a known exploit (check the Trivy title for "actively exploited" or CISA KEV references)
- Any CVE with a remote code execution (RCE) vector, regardless of exposure
**Upgrade Soon** — Act within 2 weeks:
- CRITICAL CVEs in LAN-only services
- HIGH CVEs in LAN-only services that have a fix available
**Info Only** — Track but no rush:
- HIGH CVEs with no fix available yet
- CVEs in supporting infrastructure containers (databases, Redis) that aren't directly reachable
- CVEs in packages the service doesn't actively use (e.g., a vulnerable library bundled in the base image but not imported)
### What to Include in the Report
1. **Unmanaged containers** — Flag any images found by the scan that aren't in the Ansible-managed inventory above. These are a governance risk: no automated update path, no config-as-code, no rollback plan.
2. **Per-tier findings** grouped by urgency (Upgrade Now / Upgrade Soon / Info Only)
3. **For each affected service:**
- Image name and exposure level (internet / LAN / internal-only)
- Count of CRITICAL and HIGH CVEs
- The 2-3 most impactful CVEs by name with a one-line description
- Whether a fix is available (fixed version noted by Trivy)
- The specific `ansible-playbook` command to update: `ansible-playbook -i inventory.yaml deploy_<service>.yaml --vault-password-file ~/ansible_key`
4. **Skip noise** — Don't list every CVE. Focus on the ones that matter given the exposure model.
### Update Commands Reference
| Service | Playbook |
|---------|----------|
| Arr suite (Sonarr, Radarr, etc.) | `deploy_arr.yaml` |
| Grafana stack | `deploy_grafana.yaml` |
| Homepage | `deploy_homepage.yaml` |
| Jellyfin | `deploy_jellyfin.yaml` |
| JellyPlex-Watched | `deploy_jellyplex_watched.yaml` |
| Jellyseerr | `deploy_jellyseerr.yaml` |
| Maintainerr | `deploy_maintainerr.yaml` |
| n8n | `deploy_n8n.yaml` |
| Overseerr | `deploy_overseerr.yaml` |
| Paperless-NGX | `deploy_paperless.yaml` |
| Plex | `deploy_plex.yaml` |
| Proxy (SWAG) | `deploy_proxy.yaml` |
| Tracearr | `deploy_tracearr.yaml` |
| All containers (pull latest) | `update_docker_containers.yaml` |
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!