tutorial

Monitoring Vuls Vulnerability Scanner with Vigilmon

Vuls is a Go-based agentless vulnerability scanner — here's how to monitor scan completion rates, CVE database freshness, critical CVE detection, host scan coverage, and SSH reachability with Vigilmon.

Vuls is an open source agentless vulnerability scanner for Linux and FreeBSD systems, written in Go by Future Corporation. It connects to remote hosts via SSH, enumerates installed packages, and correlates them against CVE databases (NVD, OVAL, Red Hat, Ubuntu, Debian) to produce vulnerability reports — all without installing any agent on target systems. For teams running continuous vulnerability management across large server fleets, Vuls is only valuable when it's actually scanning. Scan failures, stale CVE databases, and missing hosts create invisible blind spots — you think you're covered, but you're not. Vigilmon gives you visibility into Vuls itself: scan completion, database freshness, CVE detection, and host coverage.

What You'll Set Up

  • Scan completion rate monitor via cron heartbeat
  • CVE database freshness alert (go-cve-dictionary and goval-dictionary)
  • Critical CVE detection notification
  • Host scan coverage monitor
  • SSH reachability rate tracking
  • VulsRepo web health check (if deployed)
  • Alert channels with appropriate thresholds

Prerequisites

  • Vuls installed and configured with a config.toml pointing at target hosts
  • go-cve-dictionary and goval-dictionary installed and populated
  • Scheduled Vuls scans via cron
  • A free Vigilmon account

Step 1: Monitor Scan Completion Rate

The most critical Vuls health signal is whether scans are completing. A Vuls scan that silently fails — due to SSH key changes, a target host going down, or a Vuls binary crash — leaves you with no vulnerability data for that host. Use a Vigilmon heartbeat to detect scan failures.

Wrap your Vuls scan command in a script that sends a heartbeat only on full success:

#!/bin/bash
SCAN_LOG="/var/log/vuls/scan-$(date +%Y%m%d).log"
REPORT_LOG="/var/log/vuls/report-$(date +%Y%m%d).log"

# Run Vuls scan
vuls scan -config=/etc/vuls/config.toml 2>&1 | tee "$SCAN_LOG"
SCAN_EXIT=${PIPESTATUS[0]}

if [ "$SCAN_EXIT" -ne 0 ]; then
    echo "Vuls scan failed with exit code $SCAN_EXIT" >&2
    exit 1
fi

# Run Vuls report (correlate with CVE databases)
vuls report -config=/etc/vuls/config.toml 2>&1 | tee "$REPORT_LOG"
REPORT_EXIT=${PIPESTATUS[0]}

if [ "$REPORT_EXIT" -ne 0 ]; then
    echo "Vuls report failed with exit code $REPORT_EXIT" >&2
    exit 1
fi

# Both succeeded — send heartbeat
curl -fsS "https://vigilmon.online/api/v1/heartbeat/YOUR_SCAN_HEARTBEAT" > /dev/null
echo "Vuls scan and report completed successfully"

Make executable and add to cron:

0 2 * * * /usr/local/bin/vuls-scan.sh

Create the heartbeat in Vigilmon with a 25-hour grace period — if a scheduled daily scan misses its window (due to scan duration overrun or failure), Vigilmon alerts you before the next scheduled scan would run.

For longer scans (large fleets), adjust the grace period to match your expected scan duration plus a safety buffer.


Step 2: Monitor CVE Database Freshness

Vuls correlates installed packages against CVE data fetched by go-cve-dictionary and goval-dictionary. If these databases aren't updated daily, Vuls will miss newly published CVEs — creating a false sense of security. Monitor database freshness with a timestamp check:

#!/bin/bash
# Path to go-cve-dictionary database
CVE_DB="/var/lib/go-cve-dictionary/cve.sqlite3"
OVAL_DB="/var/lib/goval-dictionary/oval.sqlite3"

MAX_AGE_HOURS=25  # Alert if not updated in 25 hours

now=$(date +%s)

check_db_freshness() {
    local db_path="$1"
    local db_name="$2"

    if [ ! -f "$db_path" ]; then
        echo "$db_name database not found at $db_path" >&2
        return 1
    fi

    local mtime=$(stat -c %Y "$db_path")
    local age_hours=$(( (now - mtime) / 3600 ))

    if [ "$age_hours" -gt "$MAX_AGE_HOURS" ]; then
        echo "$db_name database is ${age_hours}h old (threshold: ${MAX_AGE_HOURS}h)" >&2
        return 1
    fi

    return 0
}

CVE_OK=0
OVAL_OK=0

check_db_freshness "$CVE_DB" "go-cve-dictionary" && CVE_OK=1
check_db_freshness "$OVAL_DB" "goval-dictionary" && OVAL_OK=1

if [ "$CVE_OK" -eq 1 ] && [ "$OVAL_OK" -eq 1 ]; then
    curl -fsS "https://vigilmon.online/api/v1/heartbeat/YOUR_CVE_DB_HEARTBEAT" > /dev/null
fi
0 */6 * * * /usr/local/bin/check-vuls-db.sh

Create the heartbeat with a 7-hour grace period. CVE database updates should run daily — checking freshness every 6 hours catches a failed update within half a day, giving you time to re-run the fetch before the next scan.

Your CVE fetch cron should look like:

0 1 * * * go-cve-dictionary fetchnvd -years $(date +%Y) -dbpath /var/lib/go-cve-dictionary/cve.sqlite3
0 1 * * * goval-dictionary fetch-redhat --dbpath /var/lib/goval-dictionary/oval.sqlite3

Step 3: Alert on Critical CVE Detection

The primary output of Vuls is CVE reports. When a new critical CVE (CVSS 9.0+) is discovered on any host, you need immediate notification — not a report buried in a log file. Use a post-scan script that parses Vuls output and sends a Vigilmon alert:

#!/bin/bash
RESULTS_DIR="/var/log/vuls/results"
LATEST_RESULT=$(ls -t "$RESULTS_DIR"/*.json 2>/dev/null | head -1)

if [ -z "$LATEST_RESULT" ]; then
    echo "No Vuls results found in $RESULTS_DIR" >&2
    exit 1
fi

# Count critical CVEs (CVSS >= 9.0) across all hosts
CRITICAL_COUNT=$(python3 -c "
import json, sys

with open('$LATEST_RESULT') as f:
    data = json.load(f)

critical = 0
for host, host_data in data.get('servers', {}).items():
    for cve_id, cve in host_data.get('vulnInfos', {}).items():
        cvss = cve.get('cvssScore', 0)
        if cvss >= 9.0:
            critical += 1
            print(f'CRITICAL: {cve_id} (CVSS {cvss}) on {host}', file=sys.stderr)

print(critical)
" 2>/tmp/critical-cves.txt)

echo "Found $CRITICAL_COUNT critical CVEs"

# Send alert if any critical CVEs found
if [ "$CRITICAL_COUNT" -gt 0 ]; then
    # Log critical CVEs for review
    cat /tmp/critical-cves.txt >> /var/log/vuls/critical-alerts.log

    # Trigger a Vigilmon alert via a failed heartbeat (don't send the beat)
    echo "Critical CVEs detected - not sending heartbeat (alert will fire)"
else
    # No critical CVEs — send heartbeat to indicate clean scan
    curl -fsS "https://vigilmon.online/api/v1/heartbeat/YOUR_CRITICAL_CVE_HEARTBEAT" > /dev/null
fi

Create a Vigilmon heartbeat with a 25-hour grace period. Add this check at the end of your scan script (after vuls report). When critical CVEs are found, the heartbeat is intentionally not sent — Vigilmon alerts you after the grace period expires.

This inverted pattern — heartbeat means "no critical CVEs" — lets you use Vigilmon's alerting infrastructure for security events without requiring a separate notification system.


Step 4: Monitor Host Scan Coverage

Vuls must scan every production host on schedule. Hosts can silently drop out of scan coverage when SSH keys are rotated, firewall rules change, or hosts are added without being registered in config.toml. Monitor the ratio of successfully scanned hosts to expected hosts:

#!/bin/bash
RESULTS_DIR="/var/log/vuls/results"
LATEST_RESULT=$(ls -t "$RESULTS_DIR"/*.json 2>/dev/null | head -1)
CONFIG="/etc/vuls/config.toml"

if [ -z "$LATEST_RESULT" ]; then
    echo "No scan results found" >&2
    exit 1
fi

# Count hosts in config.toml
EXPECTED_HOSTS=$(grep -c '^\[servers\.' "$CONFIG" 2>/dev/null || echo 0)

# Count hosts successfully scanned in latest result
SCANNED_HOSTS=$(python3 -c "
import json
with open('$LATEST_RESULT') as f:
    data = json.load(f)
print(len(data.get('servers', {})))
" 2>/dev/null)

echo "Expected: $EXPECTED_HOSTS hosts | Scanned: $SCANNED_HOSTS hosts"

# Alert if scanned count is below 90% of expected
THRESHOLD=$(echo "$EXPECTED_HOSTS * 0.90" | bc | cut -d. -f1)

if [ "$SCANNED_HOSTS" -ge "$THRESHOLD" ] 2>/dev/null; then
    curl -fsS "https://vigilmon.online/api/v1/heartbeat/YOUR_COVERAGE_HEARTBEAT" > /dev/null
else
    echo "Coverage below threshold: $SCANNED_HOSTS/$EXPECTED_HOSTS (threshold: $THRESHOLD)" >&2
fi
30 2 * * * /usr/local/bin/check-vuls-coverage.sh

Schedule this 30 minutes after your scan completes. Create the heartbeat with a 25-hour grace period. A drop in scan coverage — even one missing host — means a gap in your vulnerability visibility.


Step 5: Monitor SSH Reachability Rate

Vuls depends entirely on SSH connectivity to target hosts. SSH failures — expired keys, firewall changes, host reboots — silently fail without alerting you until the next scan cycle. Run a periodic SSH reachability check independent of Vuls scans:

#!/bin/bash
CONFIG="/etc/vuls/config.toml"

# Extract SSH targets from config.toml
HOSTS=$(python3 -c "
import re, sys

with open('$CONFIG') as f:
    content = f.read()

# Extract host entries: [servers.hostname]
blocks = re.findall(r'\[servers\.(\w+)\].*?host\s*=\s*\"([^\"]+)\".*?port\s*=\s*(\d+)', content, re.DOTALL)
for name, host, port in blocks:
    print(f'{name}:{host}:{port}')
" 2>/dev/null)

TOTAL=0
REACHABLE=0

while IFS=: read -r name host port; do
    TOTAL=$((TOTAL + 1))
    if ssh -o ConnectTimeout=5 -o BatchMode=yes -p "$port" "vuls@$host" "true" 2>/dev/null; then
        REACHABLE=$((REACHABLE + 1))
    else
        echo "SSH unreachable: $name ($host:$port)" >&2
    fi
done <<< "$HOSTS"

echo "SSH reachability: $REACHABLE/$TOTAL"

THRESHOLD=$(echo "$TOTAL * 0.95" | bc | cut -d. -f1)

if [ "$REACHABLE" -ge "$THRESHOLD" ] 2>/dev/null; then
    curl -fsS "https://vigilmon.online/api/v1/heartbeat/YOUR_SSH_HEARTBEAT" > /dev/null
fi
0 */4 * * * /usr/local/bin/check-vuls-ssh.sh

Create the heartbeat with a 5-hour grace period. Check SSH reachability every 4 hours — this catches hosts that became unreachable between scan cycles, giving you time to investigate before the next scan would silently miss them.


Step 6: Monitor VulsRepo (If Deployed)

VulsRepo is an optional web-based vulnerability report viewer for Vuls results. If you've deployed it, monitor its availability as a separate HTTP check:

  1. Log in to vigilmon.online and click Add Monitor.
  2. Type: HTTP / HTTPS
  3. URL: http://your-vulsrepo-host:5111 (or your configured port/domain)
  4. Expected HTTP status: 200
  5. Keyword check: VulsRepo or Vuls
  6. Check interval: 5 minutes
  7. Click Save.

VulsRepo is a reporting interface — an outage doesn't break vulnerability scanning itself, but it blocks security teams from reviewing reports. Set alert severity to Warning rather than Critical.

Also monitor report file generation as part of your scan completion script:

# After vuls report completes, check the JSON result was written
RESULT_FILE="/var/log/vuls/results/$(date +%Y-%m-%dT%H).json"
if [ -f "$RESULT_FILE" ] && [ -s "$RESULT_FILE" ]; then
    curl -fsS "https://vigilmon.online/api/v1/heartbeat/YOUR_REPORT_HEARTBEAT" > /dev/null
fi

Step 7: Configure Alert Channels

Set up alert routing in Vigilmon for your Vuls monitoring stack:

  1. Go to Alert Channels and add email, Slack, or PagerDuty.
  2. Apply thresholds:

| Monitor | Alert After | Severity | |---|---|---| | Scan completion heartbeat | 1 missed beat | Critical | | CVE database freshness | 1 missed beat | High | | Critical CVE detection | 1 missed beat | Critical | | Host scan coverage | 1 missed beat | High | | SSH reachability | 1 missed beat | Warning | | VulsRepo web UI | 3 consecutive failures | Warning | | Report generation | 1 missed beat | High |

  1. For Critical CVE detection and scan completion failures, route alerts to a security channel or on-call rotation — these represent gaps in your vulnerability management program.

  2. For CVE database freshness, route to a lower-priority channel (email, not PagerDuty) — stale CVE data is serious but not an immediate incident.


Step 8: Track Scan Duration

Long Vuls scans indicate slow SSH targets, overloaded hosts, or network congestion between the Vuls server and targets. Track scan duration and alert if it grows unexpectedly:

#!/bin/bash
START_TIME=$(date +%s)

# Run your Vuls scan
vuls scan -config=/etc/vuls/config.toml 2>&1 | tee /var/log/vuls/scan.log
SCAN_EXIT=${PIPESTATUS[0]}

END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
DURATION_MINUTES=$((DURATION / 60))

echo "Scan duration: ${DURATION_MINUTES} minutes"

# Alert if scan takes more than 30 minutes (tune for your fleet size)
MAX_MINUTES=30

if [ "$SCAN_EXIT" -eq 0 ] && [ "$DURATION_MINUTES" -lt "$MAX_MINUTES" ]; then
    curl -fsS "https://vigilmon.online/api/v1/heartbeat/YOUR_DURATION_HEARTBEAT" > /dev/null
elif [ "$DURATION_MINUTES" -ge "$MAX_MINUTES" ]; then
    echo "Scan took ${DURATION_MINUTES}m — exceeds ${MAX_MINUTES}m threshold" >&2
fi

Log scan durations to a file for trend analysis:

echo "$(date -Iseconds) duration_minutes=${DURATION_MINUTES}" >> /var/log/vuls/scan-durations.log

A growing scan duration trend — even without triggering the absolute threshold — indicates a problem with one or more SSH targets that will eventually cause scan timeouts.


Why Monitoring Vuls Matters

Vuls is your eyes into the vulnerability state of your infrastructure. When Vuls itself is broken, you have zero vulnerability visibility — but you don't know it. The risks are subtle:

Silent scan failures create false confidence: A failed Vuls scan produces no output — no report, no error visible to security teams. Without a scan completion monitor, a broken Vuls setup can go undetected for weeks while your team believes they have current vulnerability data.

Stale CVE data misses recent vulnerabilities: A newly published critical CVE that Vuls doesn't know about is invisible in reports. CVE databases must be refreshed daily; a freshness monitor ensures you're matching packages against current threat intelligence.

Host coverage gaps hide attack surface: Infrastructure changes — new servers, SSH key rotations, firewall updates — can silently remove hosts from scan coverage. A coverage monitor catches these gaps before they become exploitable blind spots.

Vigilmon closes the monitoring gap in your vulnerability management pipeline, ensuring that Vuls is actively scanning, using current CVE data, and covering your full host inventory.


Ready to add observability to your Vuls deployment? Create a free Vigilmon account and set up your first scan completion heartbeat in minutes.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →