|
| 1 | +Dependency Vulnerability Scanning (OSV) |
| 2 | +======================================= |
| 3 | + |
| 4 | +``build_sbom`` *inventories* dependencies and ``to_sarif`` *exports* findings, |
| 5 | +but nothing in between ever **produced** a vulnerability finding — there was no |
| 6 | +advisory matching at all. This closes that loop: given the SBOM's |
| 7 | +``(ecosystem, name, version)`` components and an `OSV <https://osv.dev>`_ |
| 8 | +advisory database, ``scan_components`` reports which packages are affected, and |
| 9 | +``findings_to_sarif`` bridges the results straight into the existing SARIF |
| 10 | +exporter for GitHub / Azure DevOps code scanning. |
| 11 | + |
| 12 | +The advisory database is **injected as plain data** (a list of OSV records), so |
| 13 | +matching is fully offline and deterministic; the live ``osv.dev`` query is a |
| 14 | +separate, optional ``fetcher`` seam. Pure standard library (``re``); imports no |
| 15 | +``PySide6``. |
| 16 | + |
| 17 | +Headless API |
| 18 | +------------ |
| 19 | + |
| 20 | +.. code-block:: python |
| 21 | +
|
| 22 | + from je_auto_control import ( |
| 23 | + build_sbom, scan_components, findings_to_sarif, write_sarif) |
| 24 | +
|
| 25 | + advisories = [{ |
| 26 | + "id": "GHSA-foo", "summary": "RCE in foo", |
| 27 | + "database_specific": {"severity": "HIGH"}, |
| 28 | + "affected": [{ |
| 29 | + "package": {"ecosystem": "PyPI", "name": "foo"}, |
| 30 | + "ranges": [{"type": "ECOSYSTEM", |
| 31 | + "events": [{"introduced": "0"}, {"fixed": "1.2.0"}]}], |
| 32 | + }], |
| 33 | + }] |
| 34 | +
|
| 35 | + sbom = build_sbom("je_auto_control") |
| 36 | + findings = scan_components(sbom["components"], advisories) |
| 37 | + # findings: [{id, package, version, summary, severity, fixed, aliases}] |
| 38 | + write_sarif(findings_to_sarif(findings), "vulns.sarif", |
| 39 | + tool_name="AutoControl-VulnScan") |
| 40 | +
|
| 41 | +``is_affected(version, osv_range)`` evaluates one OSV range by sweeping its |
| 42 | +``introduced`` / ``fixed`` / ``last_affected`` events; ``match_package`` checks |
| 43 | +a single package against the database (explicit ``versions`` **or** ranges); |
| 44 | +``scan_components`` runs the whole SBOM. Package names are compared with PEP-503 |
| 45 | +normalization, and the OSV severity word (``CRITICAL`` / ``HIGH`` / |
| 46 | +``MODERATE`` / ``LOW``) maps to a SARIF ``error`` / ``warning`` / ``note`` |
| 47 | +level (defaulting to ``warning``). |
| 48 | + |
| 49 | +Version ordering is a pragmatic numeric comparison: release components compare |
| 50 | +as integers and a pre-release suffix (``1.2.0-rc1``) sorts before the final |
| 51 | +release. Git ranges and full CVSS-vector scoring are out of scope. |
| 52 | + |
| 53 | +Live advisories (optional) |
| 54 | +-------------------------- |
| 55 | + |
| 56 | +Pass a ``fetcher`` callable to pull advisories at scan time (e.g. from the |
| 57 | +``osv.dev`` API). Tests inject a fake fetcher, so the matching logic is verified |
| 58 | +without any network: |
| 59 | + |
| 60 | +.. code-block:: python |
| 61 | +
|
| 62 | + findings = scan_components(sbom["components"], None, |
| 63 | + fetcher=my_osv_fetcher) |
| 64 | +
|
| 65 | +Executor command |
| 66 | +---------------- |
| 67 | + |
| 68 | +``AC_scan_vulns`` takes ``components`` (a component list, a full SBOM dict, or a |
| 69 | +JSON string) and ``advisories`` (a list or JSON string), with an optional |
| 70 | +``sarif_path`` to write a SARIF report; it returns ``{findings, count}`` (plus |
| 71 | +``sarif_path`` when written). The same operation is exposed as the MCP tool |
| 72 | +``ac_scan_vulns`` and as a Script Builder command under **Security**. |
0 commit comments