tenable-rss-processor
Status: Live Source: Tenable CVE RSS feeds Type:
rss(two feeds:sort=updatedandsort=newest) Source slug:tenableSchedule: seecron_matchfortenable-rss-processorinscripts/task-manager.toml.
Overview
Tenable publishes its own reading of each CVE — severity, affected products and
research notes — under its own TNCVE- identifier. That assessment is worth
holding separately from the CNA’s, because the two disagree often enough that
the disagreement is itself signal.
Two feeds are walked on every run:
?sort=updated— entries whose analysis Tenable has revised?sort=newest— entries Tenable has just published
Walking both is deliberate. newest alone would miss revisions to existing
analysis, and updated alone would lag on first publication.
Identifier policy: the record is keyed TNCVE-YYYY-NNNNN, formed by
replacing the CVE- prefix on the upstream id. The original CVE-YYYY-NNNN is
written as a CVEAlias edge through db.InsertAliases, so a lookup on either
identifier resolves to the other. This is what keeps the source useful rather
than a parallel namespace — a consumer querying a CVE sees Tenable’s view
without knowing the TNCVE scheme exists.
Resume is content-based: a SHA is tracked per CVE in sourceFileHash, so a
revised entry is re-read and an unchanged one is skipped. --force reprocesses
everything.
Records produced
| Condition | Records |
|---|---|
| Every advisory | CVEMetadata (source="tenable", cveId=TNCVE-…), CVEDescription, CVEMetadataReferences |
| Advisory carries a score | CVEMetric — not CVEMetadata.vectorString |
| Always | CVEAlias edge linking TNCVE-… to the original CVE-… |
Where the CVSS lives
This processor writes CVSS to CVEMetric, and leaves
CVEMetadata.vectorString NULL. Anyone measuring coverage from
vectorString will read this source as having no CVSS at all, which is wrong:
in production it holds 35,628 CVEMetric rows covering 34,438 of 34,714 CVEs
(99.2%). CVEMetric is the authority for whether a record is scored; the
scalar column on CVEMetadata is a denormalised convenience that several
processors, including this one, do not populate.
Measured efficacy
A full production backfill on 2026-08-06 (just go-tenable-rss-backfill prod)
processed 657 advisories with zero errors, taking the row count from 34,291 to
34,714. Post-run state:
| Metric | Value |
|---|---|
| Rows | 34,714 |
| With a title | 34,714 (100%) |
With sourceAdvisoryRef | 34,714 (100%) |
CVEDescription rows | 34,714 |
CVEMetric rows / CVEs covered | 35,628 / 34,438 (99.2%) |
CVEAlias edges from this source | 73,474 |
datePublished = 0 | 0 |
| Content-free orphan rows | 0 |
Zero dateless rows and zero orphan rows are unusual across this fleet and worth preserving as a regression baseline.
Known issues
defaultTimeout = 30 * time.Minuteis applied unconditionally rather than viainternal/rundeadline.Softgated onEXPECTED_DURATION_MINUTES. That violates the “backfill must not have a deadline” rule, so a local backfill can be truncated silently. The 657-advisory run above finished well inside the window, so it did not bite here.