Security Engineering

Qualys VMDR vs Tenable.io: Integration Patterns for a Downstream Prioritization Layer

Mara Osei · · 10 min read
Qualys and Tenable scanner integration normalization patterns

Qualys VMDR and Tenable.io are the two dominant vulnerability scanners in mid-market security programs. If you're running both — or if you're building a prioritization layer that needs to ingest from either — you will spend a meaningful amount of time in the gap between their data models. The gap is not accidental. Each scanner has made deliberate design choices about how to represent assets, findings, and severity. Those choices reflect real engineering tradeoffs, but they create friction when you try to merge their output downstream.

This post documents the specific integration patterns we use in Vendrsec's ingestion layer for both platforms: where the data models diverge, how we normalize them, and the edge cases that burned us before we built explicit handling for them.

The Fundamental Asset Model Difference

Qualys VMDR's primary asset identifier is the Host ID — an internal integer assigned by Qualys to each managed host. Host IDs are stable across scans, persist through IP changes, and are the recommended way to track assets in the Qualys asset model. The Host ID is your canonical key in Qualys-world.

Tenable.io's equivalent is the Asset UUID — a UUID assigned when Tenable first discovers the asset. Asset UUIDs are also stable and persist through IP changes, but the discovery logic that generates them differs from Qualys. Tenable uses a combination of IP address, MAC address, and a set of host attributes (NetBIOS name, FQDN, operating system) to determine whether a newly-scanned host is "the same asset" as a previously-known one. The merge logic is documented but the weighting is opaque.

The practical consequence: the same physical host can have a Qualys Host ID of 874391 and a Tenable Asset UUID of 3f8a2b1c-..., and there is no built-in mapping between them. Your ingestion layer needs to build that mapping, and it needs to handle the cases where the two scanners have legitimately split what you think of as one asset into two.

Severity Mapping: Where the Labels Lie

Both scanners report severity, but the scales don't align. Qualys uses a 1-5 integer scale (1 = Minimal, 5 = Critical). Tenable uses CVSS 3.x base score bands mapped to labels (Critical: 9.0-10.0, High: 7.0-8.9, Medium: 4.0-6.9, Low: 0.1-3.9). CVSS is the lingua franca, but Qualys's 1-5 scale incorporates temporal and environmental factors that CVSS base score alone doesn't capture.

When Qualys rates a finding as severity 5 (Critical) but the underlying CVE has a CVSS base score of 7.4 (which Tenable would label High), that discrepancy is meaningful. Qualys's QID logic may have incorporated exploitation data, vendor-specific context, or environmental factors that pushed the severity up. Blindly mapping Qualys 5 → CVSS 9+ loses that signal.

We're not saying CVSS base score is the right normalization target — it's just the lowest common denominator. The right approach for a downstream prioritization layer is to preserve both: the CVSS base score for comparability across scanners, and the source scanner's severity label as a separate field that can be factored into scoring independently.

In practice, our normalized finding schema carries cvss_base_score (from NVD, not the scanner), qualys_severity (1-5 if source is Qualys), and scanner_severity_label (the scanner's own label, verbatim). Downstream prioritization can weight all three.

API Access Patterns: What Actually Matters at Scale

Qualys VMDR exposes findings via the VMDR APIs. The most useful endpoint for bulk finding ingestion is the Host List Detection API, which returns all detections for a host or group of hosts. Pagination is asset-based — you page through hosts, and each host record includes its detections inline. Rate limits are per-subscription tier and can be a real constraint if you're trying to sync a large asset inventory frequently.

Tenable.io's exports API is the right path for bulk ingestion, not the real-time vulnerability findings endpoints. The exports API queues an async export job, and you poll for completion. This is more efficient than paginating through findings synchronously for large environments, but it introduces latency — an export job for a 5,000-asset environment typically completes in 3-8 minutes. If you're trying to build near-real-time correlation, that latency matters.

One integration pattern we use: Tenable webhooks for new critical findings, combined with bulk export for full-inventory syncs. The webhook fires immediately when a new critical-severity finding is created; the bulk export keeps the full inventory normalized on a daily cadence. This gives you responsiveness for high-priority items without hammering the export API constantly.

Finding Identifier Stability: The Reopened Finding Problem

Both scanners have the concept of a "reopened" finding — a vulnerability that was previously marked as fixed but reappears in a subsequent scan. The identifiers work differently.

In Qualys, a detection is keyed on (Host ID, QID). If a detection is marked as fixed and then reappears, the same (Host ID, QID) key is reused — the detection history is appended with a new "reopened" event. The detection ID stays stable.

In Tenable, the finding is keyed on a plugin_output hash that changes if the finding details change. A reopened finding may or may not reuse the same identifier depending on whether the plugin output changed. In practice, plugin output often changes between scan runs even for the same underlying vulnerability, because Tenable includes OS version strings, installed package versions, and other variable output in the plugin output text.

The consequence for a downstream system: if you're tracking "has this specific finding been remediated," you need to handle Tenable's identifier instability explicitly. Our approach is to use (asset_uuid, cve_id, plugin_family) as the stable deduplication key rather than the Tenable finding ID, with the plugin ID stored as a reference but not used as a primary key.

Scan Coverage Gaps: What Each Scanner Misses

Qualys VMDR is comprehensive for traditional host-based scanning and has strong authenticated scan coverage for OS and installed software. Its cloud-native coverage (CSPM) has improved but still lags dedicated cloud security tools for some vulnerability classes.

Tenable.io covers a wider range of network devices (OT/IoT, network infrastructure) and has strong web application scanning coverage in Tenable.io's WAS component. Its agent-based scanning (Nessus Agent) has slightly better coverage in environments where you can't run credentialed network scans.

Neither covers container-level vulnerability scanning as comprehensively as dedicated container scanners. Neither covers serverless function vulnerabilities at the same depth as Wiz or similar cloud-native tools.

The practical implication: a normalized findings schema that ingests from both Qualys and Tenable will still have coverage gaps for cloud-native and container workloads. Those gaps are the reason most mid-market security teams run three or more scanners, and why the correlation problem doesn't fully go away even after you've solved the Qualys/Tenable normalization layer.

What to Build vs. What to Buy

If your environment is purely Qualys or purely Tenable, building a direct integration to a downstream prioritization layer is tractable. The API surface is well-documented, the data models are consistent, and the normalization problem is bounded.

The complexity compounds when you add the second scanner, and again when you add a cloud-native scanner. Each pairwise combination introduces asset identity reconciliation, severity normalization, and temporal sync issues. The integration work is not linear — it's closer to O(n²) in the number of scanners, because each new scanner needs to be reconciled with each existing one.

We built this infrastructure because it's the foundation that everything else in Vendrsec depends on. If you're evaluating whether to build it internally, the honest answer is: it's doable, it takes 3-5 months of engineering time to do well, and it will require ongoing maintenance as scanner APIs evolve. The alternative is using a prioritization layer that's already built the normalization logic and gives your team's engineering time back for the actual security work.

See these principles in action