From 327b0c0ea71a0e0e1ddeb00c27a0e40927ce090b Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Thu, 21 Nov 2024 10:39:59 -0600 Subject: [PATCH] Add validation for OSV records (#208) --- .github/osv-schema.json | 438 +++++++++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 16 ++ .pre-commit-config.yaml | 7 + 3 files changed, 461 insertions(+) create mode 100644 .github/osv-schema.json create mode 100644 .github/workflows/ci.yml create mode 100644 .pre-commit-config.yaml diff --git a/.github/osv-schema.json b/.github/osv-schema.json new file mode 100644 index 00000000..ab82fc77 --- /dev/null +++ b/.github/osv-schema.json @@ -0,0 +1,438 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/ossf/osv-schema/main/validation/schema.json", + "title": "Open Source Vulnerability", + "description": "A schema for describing a vulnerability in an open source package. See also https://ossf.github.io/osv-schema/", + "type": "object", + "properties": { + "schema_version": { + "type": "string" + }, + "id": { + "$ref": "#/$defs/prefix" + }, + "modified": { + "$ref": "#/$defs/timestamp" + }, + "published": { + "$ref": "#/$defs/timestamp" + }, + "withdrawn": { + "$ref": "#/$defs/timestamp" + }, + "aliases": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "related": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "details": { + "type": "string" + }, + "severity": { + "$ref": "#/$defs/severity" + }, + "affected": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "package": { + "type": "object", + "properties": { + "ecosystem": { + "$ref": "#/$defs/ecosystemWithSuffix" + }, + "name": { + "type": "string" + }, + "purl": { + "type": "string" + } + }, + "required": [ + "ecosystem", + "name" + ] + }, + "severity": { + "$ref": "#/$defs/severity" + }, + "ranges": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "GIT", + "SEMVER", + "ECOSYSTEM" + ] + }, + "repo": { + "type": "string" + }, + "events": { + "title": "events must contain an introduced object and may contain fixed, last_affected or limit objects", + "type": "array", + "contains": { + "required": [ + "introduced" + ] + }, + "items": { + "type": "object", + "oneOf": [ + { + "type": "object", + "properties": { + "introduced": { + "type": "string" + } + }, + "required": [ + "introduced" + ] + }, + { + "type": "object", + "properties": { + "fixed": { + "type": "string" + } + }, + "required": [ + "fixed" + ] + }, + { + "type": "object", + "properties": { + "last_affected": { + "type": "string" + } + }, + "required": [ + "last_affected" + ] + }, + { + "type": "object", + "properties": { + "limit": { + "type": "string" + } + }, + "required": [ + "limit" + ] + } + ] + }, + "minItems": 1 + }, + "database_specific": { + "type": "object" + } + }, + "allOf": [ + { + "title": "GIT ranges require a repo", + "if": { + "properties": { + "type": { + "const": "GIT" + } + } + }, + "then": { + "required": [ + "repo" + ] + } + }, + { + "title": "last_affected and fixed events are mutually exclusive", + "if": { + "properties": { + "events": { + "contains": { + "required": [ + "last_affected" + ] + } + } + } + }, + "then": { + "not": { + "properties": { + "events": { + "contains": { + "required": [ + "fixed" + ] + } + } + } + } + } + } + ], + "required": [ + "type", + "events" + ] + } + }, + "versions": { + "type": "array", + "items": { + "type": "string" + } + }, + "ecosystem_specific": { + "type": "object" + }, + "database_specific": { + "type": "object" + } + } + } + }, + "references": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "ADVISORY", + "ARTICLE", + "DETECTION", + "DISCUSSION", + "REPORT", + "FIX", + "INTRODUCED", + "GIT", + "PACKAGE", + "EVIDENCE", + "WEB" + ] + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "type", + "url" + ] + } + }, + "credits": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "contact": { + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "FINDER", + "REPORTER", + "ANALYST", + "COORDINATOR", + "REMEDIATION_DEVELOPER", + "REMEDIATION_REVIEWER", + "REMEDIATION_VERIFIER", + "TOOL", + "SPONSOR", + "OTHER" + ] + } + }, + "required": [ + "name" + ] + } + }, + "database_specific": { + "type": "object" + } + }, + "required": [ + "id", + "modified" + ], + "$defs": { + "ecosystemName": { + "type": "string", + "title": "Currently supported ecosystems", + "description": "These ecosystems are also documented at https://ossf.github.io/osv-schema/#affectedpackage-field", + "enum": [ + "AlmaLinux", + "Alpine", + "Android", + "Bioconductor", + "Bitnami", + "Chainguard", + "ConanCenter", + "CRAN", + "crates.io", + "Debian", + "GHC", + "GitHub Actions", + "Go", + "Hackage", + "Hex", + "Linux", + "Mageia", + "Maven", + "npm", + "NuGet", + "openSUSE", + "OSS-Fuzz", + "Packagist", + "Photon OS", + "Pub", + "PyPI", + "Red Hat", + "Rocky Linux", + "RubyGems", + "SUSE", + "SwiftURL", + "Ubuntu", + "Wolfi" + ] + }, + "ecosystemSuffix": { + "type": "string", + "pattern": ":.+" + }, + "ecosystemWithSuffix": { + "type": "string", + "title": "Currently supported ecosystems", + "description": "These ecosystems are also documented at https://ossf.github.io/osv-schema/#affectedpackage-field", + "pattern": "^(AlmaLinux|Alpine|Android|Bioconductor|Bitnami|Chainguard|ConanCenter|CRAN|crates\\.io|Debian|GHC|GitHub Actions|Go|Hackage|Hex|Linux|Mageia|Maven|npm|NuGet|openSUSE|OSS-Fuzz|Packagist|Photon OS|Pub|PyPI|Red Hat|Rocky Linux|RubyGems|SUSE|SwiftURL|Ubuntu|Wolfi|GIT)(:.+)?$" + }, + "prefix": { + "type": "string", + "title": "Currently supported home database identifier prefixes", + "description": "These home databases are also documented at https://ossf.github.io/osv-schema/#id-modified-fields", + "pattern": "^(ASB-A|PUB-A|ALSA|ALBA|ALEA|BIT|CGA|CURL|CVE|DSA|DLA|ELA|DTSA|GHSA|GO|GSD|HSEC|LBSEC|MAL|OSV|openSUSE-SU|PHSA|PSF|PYSEC|RHBA|RHEA|RHSA|RLSA|RXSA|RSEC|RUSTSEC|SUSE-[SRFO]U|UBUNTU|USN)-" + }, + "severity": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "CVSS_V2", + "CVSS_V3", + "CVSS_V4" + ] + }, + "score": { + "type": "string" + } + }, + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": "CVSS_V2" + } + } + }, + "then": { + "properties": { + "score": { + "pattern": "^((AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))/)*(AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))$" + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "CVSS_V3" + } + } + }, + "then": { + "properties": { + "score": { + "pattern": "^CVSS:3[.][01]/((AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])/)*(AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$" + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "CVSS_V4" + } + } + }, + "then": { + "properties": { + "score": { + "pattern": "^CVSS:4[.]0/AV:[NALP]/AC:[LH]/AT:[NP]/PR:[NLH]/UI:[NPA]/VC:[HLN]/VI:[HLN]/VA:[HLN]/SC:[HLN]/SI:[HLN]/SA:[HLN](/E:[XAPU])?(/CR:[XHML])?(/IR:[XHML])?(/AR:[XHML])?(/MAV:[XNALP])?(/MAC:[XLH])?(/MAT:[XNP])?(/MPR:[XNLH])?(/MUI:[XNPA])?(/MVC:[XNLH])?(/MVI:[XNLH])?(/MVA:[XNLH])?(/MSC:[XNLH])?(/MSI:[XNLHS])?(/MSA:[XNLHS])?(/S:[XNP])?(/AU:[XNY])?(/R:[XAUI])?(/V:[XDC])?(/RE:[XLMH])?(/U:(X|Clear|Green|Amber|Red))?$" + } + } + } + } + ], + "required": [ + "type", + "score" + ] + } + }, + "timestamp": { + "type": "string", + "format": "date-time", + "pattern": "[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?Z" + } + }, + "additionalProperties": false +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..bf0c73fe --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,16 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - uses: pre-commit/action@v3.0.1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..4f86168b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: +- repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.23.3 + hooks: + - id: check-jsonschema + files: "^vulns/[a-z0-9_-]+/.+\\.yaml" + args: [--schemafile, "./.github/osv-schema.json"]