Skip to content

Added plugin output, potential software vulnerability, and zero-day options to vulnerability exports - #1037

Merged
SteveMcGrath merged 5 commits into
tenable:mainfrom
lanz:RDC-25168-vuln-export-options
Oct 13, 2026
Merged

SteveMcGrath merged 5 commits into
tenable:mainfrom
lanz:RDC-25168-vuln-export-options

Conversation

@lanz

@lanz lanz commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Description

This adds five documented POST /vulns/export options to tio.exports.vulns(). Today the method can't send them: it takes only explicit keyword arguments, and the export models use extra='forbid'. The implementation follows #1027 and #1033. Every new argument defaults to None and is left out of the request when unset, so existing calls send exactly the same payload as before.

There's one commit per changelog feature, so each can be reviewed or reverted independently.

Commit Options Where they're sent
Added include_plugin_output support to vulnerability exports include_plugin_output top level
Added potential software vulnerability options to vulnerability exports include_software_vulns; software_vulns_potential, software_vulns_potential_reasons top level; filters
Added zero_day filter support to vulnerability exports zero_day filters

Two behaviors found in live testing shaped the second commit:

  • software_vulns_potential_reasons is case-sensitive on the server. The API accepts any casing without an error, but returns no results unless each value exactly matches the documented spelling (Managed, Component, Config Required, Low Fidelity, Incomplete Version, Backported). The model normalizes any casing to the documented spelling and rejects unknown values and empty lists, following the existing to_lower/to_upper validator pattern in models.py.
  • The API forbids software_vulns_potential=False combined with reasons. It returns 400 BAD_REQUEST_INVALID_VALUE, but only after the export is requested. A model validator raises a ValidationError before any request is made.

The zero_day docstring describes what the API actually does. Zero-day status comes from the CVEs on each finding's plugin, so True and False aren't complements: findings whose plugin has no CVEs match neither value, and findings whose plugin has several CVEs can match both.

No new dependencies. CHANGELOG.md and tenable/version.py are untouched.

Fixes #1036

Internal tracking: RDC-25168.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Unit tests. They use responses and add no cassettes.

  • include_plugin_output: True and False are sent at the top level, the key is left out when omitted, and invalid values raise ValidationError.
  • Software vulnerability options:
    • all three are sent in their documented locations, and omitting them keeps the previous payload;
    • reasons are normalized from any casing, both in the model and in the request exports.vulns() sends;
    • empty lists, unknown values and a bare string are rejected;
    • software_vulns_potential=False with reasons raises before the request is sent.
  • zero_day: True and False are sent under filters, the key is left out when omitted, and invalid values raise ValidationError.
  • Each new test fails against main, except the ones that check the payload is unchanged when an option is omitted. With normalization disabled, all four normalization tests fail.
  • Each commit's lint and export tests pass with only that commit checked out.
  • ruff check tenable is clean, and the full suite passes on 3.11, 3.12, 3.13 and 3.14: 2197 passed, 0 failed, 95.6–95.8% coverage.
  • bandit -ll reports no medium or high issues.
  • The Sphinx docs build is unchanged from main (exit 0, the same 3 intersphinx network warnings), and the new docstrings and examples render.

Live API tests. These used the patched exports.vulns() and its iterator against three Tenable Vulnerability Management environments, with ten-year export windows. All export jobs finished. The environment with software vulnerability data had 5180 findings.

  • include_plugin_output=False removed output from every finding in each environment (5180 and 558 findings). True kept it.
  • include_software_vulns=True added software_vulns to 648 findings.
  • software_vulns_potential=True returned only findings whose software vulnerability entries are all potential (5). False returned only confirmed ones (5175). The two exactly partition the 5180 findings.
    • The filter returns the same findings whether include_software_vulns is omitted, False or True; only True includes the software_vulns data.
  • software_vulns_potential_reasons=['managed'] was normalized to ['Managed'] and returned the 5 matching findings; sent raw in lowercase, the API returned 0.
  • software_vulns_potential=False with reasons raised ValidationError, and no export job was created.
  • zero_day was accepted, echoed in the export status, and narrowed the results (True: 541, False: 1371 of 5180). Across three environments, every plugin's findings landed in the same result set. Plugins with no CVEs matched neither value in every case, and plugins matching both values always had two or more CVEs.
  • Two of the three environments have no software vulnerability data, so the software vulnerability options were confirmed in one environment only. All other options were confirmed in every environment.

Test Configuration:

  • Python Version(s) Tested: 3.11.16, 3.12.14, 3.13.15, 3.14.7 (Windows 11)
  • Tenable.sc version (if necessary): N/A

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules (none required)

lanz added 3 commits October 3, 2026 13:24
- exports.vulns() accepts include_plugin_output, sent as a top-level boolean
  to POST /vulns/export. Setting it to False leaves the output property out of
  each exported finding. Omitting it keeps the previous payload.
- Added the field to VulnerabilityExportV1.
- Added tests for True, False, omitted, and invalid values.
…ts (RDC-25168)

- exports.vulns() accepts include_software_vulns (top-level), plus the
  software_vulns_potential and software_vulns_potential_reasons filters.
- software_vulns_potential_reasons accepts the six documented values in any
  casing and normalizes them, since the API matches them case-sensitively and
  silently returns no results for other casings. Empty lists and unknown
  values are rejected.
- Combining software_vulns_potential=False with reasons raises a validation
  error before the request, instead of the API's 400 response.
- Omitting the new arguments keeps the previous payload.
- exports.vulns() accepts the zero_day boolean filter. Omitting it keeps the
  previous payload.
- The docstring explains that zero-day status comes from the CVEs on each
  finding's plugin, so True and False are not complements: in live testing on
  three tenants, findings whose plugin has no CVEs matched neither value, and
  findings whose plugin has several CVEs could match both.
- Added tests for True, False, omitted, and invalid values.
@lanz
lanz requested a review from a team as a code owner October 3, 2026 19:20
@SteveMcGrath
SteveMcGrath merged commit 1478c7e into tenable:main Oct 13, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Support plugin output, potential software vulnerability, and zero-day options in tio.exports.vulns()

2 participants