# How OSV.dev Vulnerability Lookup Works in SkillSpector: Live API with Offline Fallback Support

> SkillSpector's SC4 performs live OSV.dev vulnerability lookup for dependencies, falling back to a static database for offline resilience. Enhance your supply chain security.

- Repository: [NVIDIA Corporation/SkillSpector](https://github.com/NVIDIA/SkillSpector)
- Tags: internals
- Published: 2026-06-20

---

**SkillSpector's supply-chain analyzer (SC4) performs a live OSV.dev vulnerability lookup for project dependencies, then automatically falls back to a curated static vulnerability database when the API is unreachable or returns incomplete coverage.**

The NVIDIA SkillSpector repository implements a robust two-stage vulnerability detection system in its supply-chain security analyzer. This architecture ensures that Python and JavaScript dependencies are checked against the latest OSV.dev advisory database while maintaining reliable offline detection through embedded fallback data. The implementation spans [`osv_client.py`](https://github.com/NVIDIA/SkillSpector/blob/main/osv_client.py) for API communication and [`static_patterns_supply_chain.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_patterns_supply_chain.py) for orchestration and offline coverage.

## How OSV.dev Vulnerability Lookup Works

The live vulnerability assessment operates through batched API queries with intelligent caching and strict timeout handling.

### Live OSV.dev API Query

For each dependency discovered in [`requirements.txt`](https://github.com/NVIDIA/SkillSpector/blob/main/requirements.txt) or [`package.json`](https://github.com/NVIDIA/SkillSpector/blob/main/package.json) files, SkillSpector constructs a query batch and invokes `query_batch()` from [`osv_client.py`](https://github.com/NVIDIA/SkillSpector/blob/main/osv_client.py). The client generates a cache key using `_cache_key()` based on the package **name**, **version**, and **ecosystem** (`ECOSYSTEM_PYPI` for PyPI or `ECOSYSTEM_NPM` for npm).

If the package is not cached, the client sends an HTTP POST request to `https://api.osv.dev/v1/querybatch` with a 10-second timeout. Upon receiving the response, `query_batch()` extracts vulnerability IDs and retrieves detailed information for up to ten IDs using `_fetch_vuln_details()`. Each entry is parsed into a `VulnResult` object and stored in the in-memory cache via `_put_cache()`.

### In-Memory Caching Strategy

The OSV client implements a time-bound cache to avoid redundant API calls during analysis runs. The `_cache` dictionary stores tuples of `(timestamp, results)` per package, with a TTL (time-to-live) of one hour defined by `_CACHE_TTL_SECS`. When `query_batch()` processes a package, it first attempts `_get_cached()` using the computed cache key; if valid cached data exists, the function returns immediately without network overhead.

### Graceful Error Handling

If the network request fails due to HTTP errors, timeouts, or malformed JSON responses, `query_batch()` returns an empty list for all packages in the batch. This signals to the orchestration layer in [`static_patterns_supply_chain.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_patterns_supply_chain.py) that the live lookup is unavailable, triggering immediate activation of the offline fallback mechanism.

## Offline Fallback for Supply Chain Security

When OSV.dev lookups fail or leave certain packages without coverage, SkillSpector seamlessly transitions to a static vulnerability database embedded in the source code.

### Static Vulnerability Database

The fallback data resides in [`static_patterns_supply_chain.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_patterns_supply_chain.py) as two constant tables: `_FALLBACK_VULNERABLE_PYPI` and `_FALLBACK_VULNERABLE_NPM`. Each entry follows the tuple structure `(package_name, max_safe_version, description, confidence)`, containing known vulnerable versions curated by the SkillSpector maintainers.

### Coverage Gap Analysis

After the live lookup completes, the `_sc4_from_osv()` function returns both the vulnerability findings and a set of covered package names (`osv_covered`). The analyzer then computes coverage gaps:

```python
uncovered_packages = [
    p for p in packages 
    if p[0].lower().replace("_", "-") not in osv_covered
]

```

The `_sc4_from_fallback()` function receives this list of uncovered packages and matches them against the static tables, ensuring no dependency escapes analysis due to API limitations.

### Severity and Version Matching

Fallback findings are automatically assigned `Severity.HIGH` regardless of the original vulnerability severity. When a static entry specifies a `max_safe` version, the code compares it against the detected version using `_version_lt()`. If the installed version is lower (and therefore vulnerable), the finding includes a remediation suggestion formatted as `>={max_safe}`.

## Implementation Details and Code Examples

The following examples demonstrate how to interact with SkillSpector's vulnerability detection components directly.

### Querying the OSV Client Directly

To check specific packages without running the full analysis pipeline:

```python
from skillspector.nodes.analyzers.osv_client import query_batch, ECOSYSTEM_PYPI

# Look up two packages simultaneously

pkgs = [("requests", "2.28.0"), ("pyyaml", None)]
results = query_batch(pkgs, ECOSYSTEM_PYPI)

for pkg, vulns in zip(pkgs, results):
    print(f"{pkg[0]}:{pkg[1] or ''} → {len(vulns)} vulnerabilities")
    for v in vulns:
        print(f"  - {v.vuln_id} ({v.severity}) – {v.summary}")

```

### Running the Full SC4 Analysis

Execute the complete supply-chain analyzer including both live and fallback stages:

```python
from skillspector.state import SkillspectorState
from skillspector.nodes.analyzers.static_patterns_supply_chain import node

# Minimal state mimicking SkillSpector's runtime

state = SkillspectorState(
    components=["requirements.txt"],
    file_cache={"requirements.txt": open("requirements.txt").read()},
    manifest={},
)

response = node(state)
for finding in response["findings"]:
    if finding.rule_id == "SC4":
        print(finding.message)

```

### Detecting Offline Mode

Check API availability before triggering analysis:

```python
from skillspector.nodes.analyzers.osv_client import is_available

if not is_available():
    print("OSV.dev is unreachable – static fallback will be used automatically.")

```

## Key Design Benefits

SkillSpector's dual-layer approach provides several architectural advantages:

- **Hybrid coverage** combines the freshness of live OSV.dev data with the reliability of static vulnerability tables
- **Graceful degradation** ensures security scanning continues uninterrupted during network outages
- **Performance optimization** through 1-hour TTL caching reduces redundant API calls for repeated package versions
- **Ecosystem flexibility** supports both PyPI and npm dependencies through unified `ECOSYSTEM` constants
- **Severity consistency** maps OSV severity levels via `_osv_severity_to_app()` while maintaining high-severity defaults for offline findings

## Summary

- SkillSpector checks dependencies via `query_batch()` in [`osv_client.py`](https://github.com/NVIDIA/SkillSpector/blob/main/osv_client.py), which queries `https://api.osv.dev/v1/querybatch` with 10-second timeouts
- In-memory caching stores results for one hour (`_CACHE_TTL_SECS`) to optimize repeated lookups
- Network failures trigger automatic fallback to `_FALLBACK_VULNERABLE_PYPI` and `_FALLBACK_VULNERABLE_NPM` in [`static_patterns_supply_chain.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_patterns_supply_chain.py)
- Uncovered packages are identified by comparing against `osv_covered` and processed by `_sc4_from_fallback()`
- Fallback findings always use `Severity.HIGH` and include safe version recommendations when `max_safe_version` is defined

## Frequently Asked Questions

### What happens when the OSV.dev API is unreachable?

When the API returns HTTP errors, times out after 10 seconds, or returns malformed JSON, `query_batch()` returns an empty list and logs a warning via `logger.warning`. The SC4 analyzer in [`static_patterns_supply_chain.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_patterns_supply_chain.py) detects this condition and immediately processes all discovered dependencies through the offline fallback tables, ensuring continuous vulnerability detection without manual intervention.

### How does SkillSpector handle cached vulnerability data?

The OSV client maintains an in-memory cache (`_cache`) that stores query results with timestamps. Before making network requests, `_get_cached()` checks if valid data exists using a cache key derived from package name, version, and ecosystem. Cached entries expire after one hour (`_CACHE_TTL_SECS`), after which `_put_cache()` updates the storage with fresh API results.

### Which package ecosystems does the OSV.dev lookup support?

SkillSpector currently supports PyPI and npm ecosystems through the constants `ECOSYSTEM_PYPI = "PyPI"` and `ECOSYSTEM_NPM = "npm"` defined in [`osv_client.py`](https://github.com/NVIDIA/SkillSpector/blob/main/osv_client.py). The analyzer automatically detects the appropriate ecosystem based on file type—[`requirements.txt`](https://github.com/NVIDIA/SkillSpector/blob/main/requirements.txt) triggers PyPI analysis while [`package.json`](https://github.com/NVIDIA/SkillSpector/blob/main/package.json) triggers npm analysis via `_extract_packages_from_requirements()` and `_extract_packages_from_package_json()` respectively.

### How are severity levels determined for fallback findings?

All fallback vulnerabilities are assigned `Severity.HIGH` regardless of their actual CVSS score or severity in the OSV database. This conservative approach ensures that offline findings receive immediate attention. In contrast, live OSV results are normalized through `_osv_severity_to_app()` to map OSV severity strings to SkillSpector's internal severity enumeration.