Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Bring the VulnCheck API to your Python applications.
- [CPE](#cpe)
- [Index](#index)
- [Indices](#indices)
- [NVD API 2.0 compatibility](#nvd-api-20-compatibility)
- [Pagination](#pagination)
- [PURL](#purl)
- [Proxy Support](#proxy-support)
Expand Down Expand Up @@ -626,6 +627,92 @@ if __name__ == "__main__":
```


</details>

### NVD API 2.0 compatibility

Query the NVD API 2.0 compatible endpoints under `/rest/json`. They take NVD's parameters and return NVD's response shapes, served from VulnCheck's data, and work with any VulnCheck API token, including community. `NvdApi` covers CVEs, CVE change history, CPEs, CPE match criteria and sources.

Look up `CVE-2021-44228`, then page through a date range using `cursor`

```python
import vulncheck_sdk
import os

TOKEN = os.environ["VULNCHECK_API_TOKEN"]

configuration = vulncheck_sdk.Configuration()
configuration.api_key["Bearer"] = TOKEN

with vulncheck_sdk.ApiClient(configuration) as api_client:
nvd_client = vulncheck_sdk.NvdApi(api_client)

# Look up a single CVE, shaped exactly as NVD's API returns it
api_response = nvd_client.nvd_cves_get(cve_id="CVE-2021-44228")
cve = api_response.vulnerabilities[0].cve
print(cve.id, cve.vuln_status)

# Walk every CVE modified in a date range. Sending an empty cursor opts in
# to cursor paging; each response then carries the cursor for the next page
cursor = ""
count = 0
while cursor is not None:
api_response = nvd_client.nvd_cves_get(
last_mod_start_date="2026-09-01T00:00:00.000",
last_mod_end_date="2026-09-02T00:00:00.000",
results_per_page=500,
cursor=cursor,
)
count += len(api_response.vulnerabilities)
print(f"Total Items: {count}/{api_response.total_results}")
cursor = api_response.next_cursor
```


<details><summary><b>Click to View Async Implementation</b></summary>

```python
import asyncio
import os
import vulncheck_sdk.aio as vcaio

TOKEN = os.environ.get("VULNCHECK_API_TOKEN")

configuration = vcaio.Configuration()
configuration.api_key["Bearer"] = TOKEN


async def main():
async with vcaio.ApiClient(configuration) as api_client:
nvd_client = vcaio.NvdApi(api_client)

# Look up a single CVE, shaped exactly as NVD's API returns it
api_response = await nvd_client.nvd_cves_get(cve_id="CVE-2021-44228")
cve = api_response.vulnerabilities[0].cve
print(cve.id, cve.vuln_status)

# Walk every CVE modified in a date range. Sending an empty cursor opts
# in to cursor paging; each response then carries the cursor for the
# next page
cursor = ""
count = 0
while cursor is not None:
api_response = await nvd_client.nvd_cves_get(
last_mod_start_date="2026-09-01T00:00:00.000",
last_mod_end_date="2026-09-02T00:00:00.000",
results_per_page=500,
cursor=cursor,
)
count += len(api_response.vulnerabilities)
print(f"Total Items: {count}/{api_response.total_results}")
cursor = api_response.next_cursor


if __name__ == "__main__":
asyncio.run(main())
```


</details>

### Pagination
Expand Down
25 changes: 25 additions & 0 deletions README.template
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Bring the VulnCheck API to your Python applications.
- [CPE](#cpe)
- [Index](#index)
- [Indices](#indices)
- [NVD API 2.0 compatibility](#nvd-api-20-compatibility)
- [Pagination](#pagination)
- [PURL](#purl)
- [Proxy Support](#proxy-support)
Expand Down Expand Up @@ -199,6 +200,30 @@ with open("./tests/indicies_aio.py", 'r') as f:

</details>

### NVD API 2.0 compatibility

Query the NVD API 2.0 compatible endpoints under `/rest/json`. They take NVD's parameters and return NVD's response shapes, served from VulnCheck's data, and work with any VulnCheck API token, including community. `NvdApi` covers CVEs, CVE change history, CPEs, CPE match criteria and sources.

Look up `CVE-2021-44228`, then page through a date range using `cursor`

<!-- [[[cog
import cog, glob
with open("./tests/nvd.py", 'r') as f:
cog.outl(f"```python\n{f.read().strip()}\n```\n")
]]] -->
<!-- [[[end]]] -->

<details><summary><b>Click to View Async Implementation</b></summary>

<!-- [[[cog
import cog, glob
with open("./tests/nvd_aio.py", 'r') as f:
cog.outl(f"```python\n{f.read().strip()}\n```\n")
]]] -->
<!-- [[[end]]] -->

</details>

### Pagination

Paginate over results for a query to VulnCheck-KEV using `cursor`
Expand Down
24 changes: 24 additions & 0 deletions tests/integration_aio.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import vulncheck_sdk.aio as vcaio
from vulncheck_sdk.aio.api.endpoints_api import EndpointsApi
from vulncheck_sdk.aio.api.indices_api import IndicesApi
from vulncheck_sdk.aio.api.nvd_api import NvdApi
from vulncheck_sdk.aio.exceptions import ApiException, UnauthorizedException

API_TOKEN = os.environ.get("VULNCHECK_API_TOKEN", "")
Expand Down Expand Up @@ -108,6 +109,28 @@ async def _check_all_indices(indices: IndicesApi) -> None:
assert status == 200


# --- NvdApi Tests ---


async def _check_nvd(api: NvdApi) -> None:
print("Testing NvdApi")
cve_id = "CVE-2021-44228"

cves = await api.nvd_cves_get(cve_id=cve_id)
assert cves.total_results == 1
assert cves.vulnerabilities[0].cve.id == cve_id

first = await api.nvd_cves_get(cve_tag="disputed", results_per_page=5, cursor="")
assert first.next_cursor
second = await api.nvd_cves_get(cve_tag="disputed", results_per_page=5, cursor=first.next_cursor)
assert second.vulnerabilities[0].cve.id != first.vulnerabilities[0].cve.id

assert (await api.nvd_cve_history_get(cve_id=cve_id)).cve_changes
assert (await api.nvd_cpes_get(cpe_match_string="cpe:2.3:a:apache:log4j", results_per_page=5)).products
assert (await api.nvd_cpe_match_get(cve_id=cve_id, results_per_page=5)).match_strings
assert (await api.nvd_source_get(results_per_page=5)).sources


# --- Orchestrator ---


Expand Down Expand Up @@ -137,6 +160,7 @@ async def main() -> None:
)

await _check_all_indices(indices_api)
await _check_nvd(vcaio.NvdApi(client))

# --- CRITICAL FIX FOR "UNCLOSED CONNECTOR" ---
# aiohttp requires a small window of time to allow the
Expand Down
52 changes: 52 additions & 0 deletions tests/integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import vulncheck_sdk
from vulncheck_sdk.api.endpoints_api import EndpointsApi
from vulncheck_sdk.api.indices_api import IndicesApi
from vulncheck_sdk.api.nvd_api import NvdApi
from vulncheck_sdk.api_response import ApiResponse
from vulncheck_sdk.exceptions import ApiException, UnauthorizedException

Expand Down Expand Up @@ -101,6 +102,49 @@ def test_all_indicies():
assert status == 200


"""
NvdApi Tests
"""

NVD_CVE_ID = "CVE-2021-44228"


def test_nvd_cves_get():
response = _get_nvd_instance(API_TOKEN).nvd_cves_get(cve_id=NVD_CVE_ID)
assert response.total_results == 1
assert response.vulnerabilities[0].cve.id == NVD_CVE_ID


def test_nvd_cves_get_cursor():
nvd = _get_nvd_instance(API_TOKEN)
first = nvd.nvd_cves_get(cve_tag="disputed", results_per_page=5, cursor="")
assert first.next_cursor
second = nvd.nvd_cves_get(cve_tag="disputed", results_per_page=5, cursor=first.next_cursor)
assert second.vulnerabilities[0].cve.id != first.vulnerabilities[0].cve.id


def test_nvd_cve_history_get():
response = _get_nvd_instance(API_TOKEN).nvd_cve_history_get(cve_id=NVD_CVE_ID)
assert response.cve_changes


def test_nvd_cpes_get():
response = _get_nvd_instance(API_TOKEN).nvd_cpes_get(
cpe_match_string="cpe:2.3:a:apache:log4j", results_per_page=5
)
assert response.products


def test_nvd_cpe_match_get():
response = _get_nvd_instance(API_TOKEN).nvd_cpe_match_get(cve_id=NVD_CVE_ID, results_per_page=5)
assert response.match_strings


def test_nvd_source_get():
response = _get_nvd_instance(API_TOKEN).nvd_source_get(results_per_page=5)
assert response.sources


"""
Helpers
"""
Expand Down Expand Up @@ -138,3 +182,11 @@ def _get_indices_instance(token: str = "") -> IndicesApi:
config.api_key["Bearer"] = token
client = vulncheck_sdk.ApiClient(config)
return vulncheck_sdk.IndicesApi(client)


def _get_nvd_instance(token: str = "") -> NvdApi:
config = vulncheck_sdk.Configuration()
if token != "":
config.api_key["Bearer"] = token
client = vulncheck_sdk.ApiClient(config)
return vulncheck_sdk.NvdApi(client)
30 changes: 30 additions & 0 deletions tests/nvd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import vulncheck_sdk
import os

TOKEN = os.environ["VULNCHECK_API_TOKEN"]

configuration = vulncheck_sdk.Configuration()
configuration.api_key["Bearer"] = TOKEN

with vulncheck_sdk.ApiClient(configuration) as api_client:
nvd_client = vulncheck_sdk.NvdApi(api_client)

# Look up a single CVE, shaped exactly as NVD's API returns it
api_response = nvd_client.nvd_cves_get(cve_id="CVE-2021-44228")
cve = api_response.vulnerabilities[0].cve
print(cve.id, cve.vuln_status)

# Walk every CVE modified in a date range. Sending an empty cursor opts in
# to cursor paging; each response then carries the cursor for the next page
cursor = ""
count = 0
while cursor is not None:
api_response = nvd_client.nvd_cves_get(
last_mod_start_date="2026-09-01T00:00:00.000",
last_mod_end_date="2026-09-02T00:00:00.000",
results_per_page=500,
cursor=cursor,
)
count += len(api_response.vulnerabilities)
print(f"Total Items: {count}/{api_response.total_results}")
cursor = api_response.next_cursor
38 changes: 38 additions & 0 deletions tests/nvd_aio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import asyncio
import os
import vulncheck_sdk.aio as vcaio

TOKEN = os.environ.get("VULNCHECK_API_TOKEN")

configuration = vcaio.Configuration()
configuration.api_key["Bearer"] = TOKEN


async def main():
async with vcaio.ApiClient(configuration) as api_client:
nvd_client = vcaio.NvdApi(api_client)

# Look up a single CVE, shaped exactly as NVD's API returns it
api_response = await nvd_client.nvd_cves_get(cve_id="CVE-2021-44228")
cve = api_response.vulnerabilities[0].cve
print(cve.id, cve.vuln_status)

# Walk every CVE modified in a date range. Sending an empty cursor opts
# in to cursor paging; each response then carries the cursor for the
# next page
cursor = ""
count = 0
while cursor is not None:
api_response = await nvd_client.nvd_cves_get(
last_mod_start_date="2026-09-01T00:00:00.000",
last_mod_end_date="2026-09-02T00:00:00.000",
results_per_page=500,
cursor=cursor,
)
count += len(api_response.vulnerabilities)
print(f"Total Items: {count}/{api_response.total_results}")
cursor = api_response.next_cursor


if __name__ == "__main__":
asyncio.run(main())
2 changes: 2 additions & 0 deletions tests/readme_examples_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"./tests/index_aio.py",
"./tests/indicies.py",
"./tests/indicies_aio.py",
"./tests/nvd.py",
"./tests/nvd_aio.py",
"./tests/pagination.py",
"./tests/pagination_aio.py",
"./tests/purl.py",
Expand Down
Loading