From ddf6a0192c59a09cf66aab7003a2c2cce3dc7e22 Mon Sep 17 00:00:00 2001 From: Jakob Linskeseder Date: Thu, 18 Nov 2021 00:04:26 +0100 Subject: [PATCH] Export advisories in OSV format Fixes #576 --- .github/workflows/export-osv.yaml | 34 +++++++++ .github/workflows/php.yaml | 36 ++++----- README.md | 3 + export-osv.php | 119 ++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/export-osv.yaml create mode 100644 export-osv.php diff --git a/.github/workflows/export-osv.yaml b/.github/workflows/export-osv.yaml new file mode 100644 index 000000000..d2d19a4d4 --- /dev/null +++ b/.github/workflows/export-osv.yaml @@ -0,0 +1,34 @@ +name: Export to OSV format + +on: + push: + branches: + - export-osv + +jobs: + publish-web: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.0" + coverage: none + tools: composer + + - run: | + git config user.name github-actions + git config user.email github-actions@github.com + composer install --prefer-dist --no-progress + php export-osv.php packagist + git add . + git stash + git fetch origin osv + git checkout osv + rm -rf packagist + git stash pop + git commit -m "Update OSV data export" || true + git push || true diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml index 718cf7f2e..b7091a409 100644 --- a/.github/workflows/php.yaml +++ b/.github/workflows/php.yaml @@ -1,27 +1,27 @@ name: Validation on: - push: - pull_request: + push: + pull_request: jobs: - run: - runs-on: ubuntu-latest + run: + runs-on: ubuntu-latest - name: Validation - steps: - - name: Checkout - uses: actions/checkout@v2 + name: Validation + steps: + - name: Checkout + uses: actions/checkout@v2 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.0" - coverage: none - tools: composer + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.0" + coverage: none + tools: composer - - name: Install dependencies - run: composer install --prefer-dist --no-progress + - name: Install dependencies + run: composer install --prefer-dist --no-progress - - name: Run tests - run: php -d memory_limit=-1 validator.php + - name: Run tests + run: php -d memory_limit=-1 validator.php diff --git a/README.md b/README.md index 3729f30bf..73963506b 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,9 @@ not** serve as the primary source of information for security issues, it is not authoritative for any referenced software, but it allows to centralize information for convenience and easy consumption. +We also export advisory data to the [OSV](https://github.com/ossf/osv-schema) format, +see the [`osv`](https://github.com/FriendsOfPHP/security-advisories/tree/osv) branch. + License ------- diff --git a/export-osv.php b/export-osv.php new file mode 100644 index 000000000..402df468c --- /dev/null +++ b/export-osv.php @@ -0,0 +1,119 @@ +getPathname()); + + return [ + 'id' => $advisory['cve'] ?? 'PHPSEC-' . $fileInfo->getBasename('.yaml'), + 'modified' => getDateFromGitLog($fileInfo), + 'published' => getDateFromGitLog($fileInfo, true), + 'aliases' => [], + 'related' => [], + 'summary' => $advisory['title'] ?? '', + 'details' => '', + 'affected' => [ + 'package' => [ + 'ecosystem' => 'Packagist', + 'name' => $package, + 'purl' => sprintf('pkg:packagist/%s', $package), + ], + 'ranges' => [ + 'type' => 'SEMVER', + 'events' => getEvents($advisory['branches']), + ], + ], + 'references' => [ + array_key_exists('link', $advisory) ? [ + 'type' => 'ADVISORY', + 'url' => $advisory['link'], + ] : null, + [ + 'type' => 'PACKAGE', + 'url' => 'https://packagist.org/packages/' . $package, + ], + ], + ]; +} + +function getEvents(array $branches): array +{ + $events = []; + + foreach (array_column($branches, 'versions') as $branch) { + if (count($branch) === 2) { + array_push($events, ['introduced' => $branch[0]]); + array_push($events, ['fixed' => $branch[1]]); + } else { + array_push($events, ['fixed' => $branch[0]]); + } + } + + return $events; +} + +function getDateFromGitLog(SplFileInfo $fileInfo, bool $created = false): string +{ + $timestamp = shell_exec(sprintf( + 'git log --format="%%at" %s %s %s %s', + $created ? '' : '--max-count 1', + $created ? '--reverse' : '', + escapeshellarg($fileInfo->getPathname()), + $created ? '| head -1' : '' + )); + + return date('Y-m-d\TH:i:s\Z', (int) trim($timestamp)); +} + +mkdir($targetFolder = $argv[1] ?? 'packagist'); + +$namespaceIterator = new DirectoryIterator(__DIR__); + +// Package namespaces +foreach ($namespaceIterator as $namespaceInfo) { + if ($namespaceInfo->isDot() || !$namespaceInfo->isDir() || $namespaceInfo->getFilename() === 'vendor' || strpos($namespaceInfo->getFilename() , '.') === 0) continue; + + $namespace = $namespaceInfo->getFilename(); + $packageIterator = new DirectoryIterator($namespaceInfo->getPathname()); + + // Packages inside namespace + foreach ($packageIterator as $packageInfo) { + if ($packageIterator->isDot() || !$packageInfo->isDir()) continue; + + $package = $packageInfo->getFilename(); + $fileSystemIterator = new FilesystemIterator($packageInfo->getPathname()); + + foreach ($fileSystemIterator as $fileInfo) { + echo 'Converting "' . $namespace . '/' . $package . '" ...' . str_repeat(' ', 20) . "\r"; + $osv = convertToOsv($fileInfo, $namespace . '/' . $package); + // TODO Handle duplicate IDs + $path = $targetFolder . DIRECTORY_SEPARATOR . $osv['id'] . '.json'; + + file_put_contents($path, json_encode($osv, JSON_PRETTY_PRINT)); + } + } +} + +echo PHP_EOL;