Skip to content

Commit e0825fa

Browse files
committed
Export advisories in OSV format
Fixes #576
1 parent 486a92e commit e0825fa

File tree

4 files changed

+182
-18
lines changed

4 files changed

+182
-18
lines changed

.github/workflows/export-osv.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Export to OSV format
2+
3+
on:
4+
push:
5+
branches:
6+
- export-osv
7+
8+
jobs:
9+
publish-web:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- uses: actions/checkout@v2
14+
with:
15+
# Required in order to extract dates from commit history
16+
fetch-depth: 0
17+
18+
- name: Setup PHP
19+
uses: shivammathur/setup-php@v2
20+
with:
21+
php-version: "8.0"
22+
coverage: none
23+
tools: composer
24+
25+
- name: Install dependencies
26+
run: composer install --prefer-dist --no-progress
27+
28+
- name: Export to OSV format
29+
run: |
30+
git config user.name github-actions
31+
git config user.email [email protected]
32+
php export-osv.php packagist
33+
git add packagist
34+
git stash
35+
git checkout osv
36+
echo `date` > published
37+
git add published
38+
git rm -r --ignore-unmatch packagist
39+
git commit -m "Update OSV data export"
40+
git stash pop
41+
git commit --amend --no-edit --allow-empty
42+
git push

.github/workflows/php.yaml

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
name: Validation
22

33
on:
4-
push:
5-
pull_request:
4+
push:
5+
pull_request:
66

77
jobs:
8-
run:
9-
runs-on: ubuntu-latest
8+
run:
9+
runs-on: ubuntu-latest
1010

11-
name: Validation
12-
steps:
13-
- name: Checkout
14-
uses: actions/checkout@v2
11+
name: Validation
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v2
1515

16-
- name: Setup PHP
17-
uses: shivammathur/setup-php@v2
18-
with:
19-
php-version: "8.0"
20-
coverage: none
21-
tools: composer
16+
- name: Setup PHP
17+
uses: shivammathur/setup-php@v2
18+
with:
19+
php-version: "8.0"
20+
coverage: none
21+
tools: composer
2222

23-
- name: Install dependencies
24-
run: composer install --prefer-dist --no-progress
23+
- name: Install dependencies
24+
run: composer install --prefer-dist --no-progress
2525

26-
- name: Run tests
27-
run: php -d memory_limit=-1 validator.php
26+
- name: Run tests
27+
run: php -d memory_limit=-1 validator.php

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ not** serve as the primary source of information for security issues, it is
77
not authoritative for any referenced software, but it allows to centralize
88
information for convenience and easy consumption.
99

10+
We also export advisory data to the [OSV](https://github.com/ossf/osv-schema) format,
11+
see the [`osv`](https://github.com/FriendsOfPHP/security-advisories/tree/osv) branch.
12+
1013
License
1114
-------
1215

export-osv.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
/**
4+
* Script for exporting advisories to OSV format.
5+
*
6+
* Usage: `php export-osv.php target_folder`
7+
*
8+
* @see https://ossf.github.io/osv-schema/
9+
*/
10+
11+
namespace FriendsOfPhp\SecurityAdvisories;
12+
13+
use DirectoryIterator;
14+
use FilesystemIterator;
15+
use SplFileInfo;
16+
use Symfony\Component\Yaml\Yaml;
17+
18+
if (!is_file($autoloader = __DIR__ . '/vendor/autoload.php')) {
19+
echo 'Dependencies are not installed, please run "composer install" first!' . PHP_EOL;
20+
exit(1);
21+
}
22+
23+
require $autoloader;
24+
25+
function convertToOsv(SplFileInfo $fileInfo, string $package): array
26+
{
27+
$advisory = Yaml::parseFile($fileInfo->getPathname());
28+
29+
return [
30+
'id' => $advisory['cve'] ?? 'PHPSEC-' . $fileInfo->getBasename('.yaml'),
31+
'modified' => getDateFromGitLog($fileInfo),
32+
'published' => getDateFromGitLog($fileInfo, true),
33+
'aliases' => [],
34+
'related' => [],
35+
'summary' => $advisory['title'] ?? '',
36+
'details' => '',
37+
'affected' => [
38+
'package' => [
39+
'ecosystem' => 'Packagist',
40+
'name' => $package,
41+
'purl' => sprintf('pkg:packagist/%s', $package),
42+
],
43+
'ranges' => [
44+
'type' => 'SEMVER',
45+
'events' => getEvents($advisory['branches']),
46+
],
47+
],
48+
'references' => [
49+
array_key_exists('link', $advisory) ? [
50+
'type' => 'ADVISORY',
51+
'url' => $advisory['link'],
52+
] : null,
53+
[
54+
'type' => 'PACKAGE',
55+
'url' => 'https://packagist.org/packages/' . $package,
56+
],
57+
],
58+
];
59+
}
60+
61+
function getEvents(array $branches): array
62+
{
63+
$events = [];
64+
65+
foreach (array_column($branches, 'versions') as $branch) {
66+
if (count($branch) === 2) {
67+
array_push($events, ['introduced' => $branch[0]]);
68+
array_push($events, ['fixed' => $branch[1]]);
69+
} else {
70+
array_push($events, ['fixed' => $branch[0]]);
71+
}
72+
}
73+
74+
return $events;
75+
}
76+
77+
function getDateFromGitLog(SplFileInfo $fileInfo, bool $created = false): string
78+
{
79+
$timestamp = shell_exec(sprintf(
80+
'git log --format="%%at" %s %s %s %s',
81+
$created ? '' : '--max-count 1',
82+
$created ? '--reverse' : '',
83+
escapeshellarg($fileInfo->getPathname()),
84+
$created ? '| head -1' : ''
85+
));
86+
87+
return date('Y-m-d\TH:i:s\Z', (int) trim($timestamp));
88+
}
89+
90+
mkdir($targetFolder = $argv[1] ?? 'packagist');
91+
92+
$namespaceIterator = new DirectoryIterator(__DIR__);
93+
94+
// Package namespaces
95+
foreach ($namespaceIterator as $namespaceInfo) {
96+
if ($namespaceInfo->isDot() || !$namespaceInfo->isDir() || $namespaceInfo->getFilename() === 'vendor' || strpos($namespaceInfo->getFilename() , '.') === 0) continue;
97+
98+
$namespace = $namespaceInfo->getFilename();
99+
$packageIterator = new DirectoryIterator($namespaceInfo->getPathname());
100+
101+
// Packages inside namespace
102+
foreach ($packageIterator as $packageInfo) {
103+
if ($packageIterator->isDot() || !$packageInfo->isDir()) continue;
104+
105+
$package = $packageInfo->getFilename();
106+
$fileSystemIterator = new FilesystemIterator($packageInfo->getPathname());
107+
108+
echo 'Converting "' . $namespace . '/' . $package . '" ...' . str_repeat(' ', 20) . "\r";
109+
110+
foreach ($fileSystemIterator as $fileInfo) {
111+
$osv = convertToOsv($fileInfo, $namespace . '/' . $package);
112+
$path = $targetFolder . DIRECTORY_SEPARATOR . $osv['id'] . '.json';
113+
114+
file_put_contents($path, json_encode($osv, JSON_PRETTY_PRINT));
115+
}
116+
}
117+
}
118+
119+
echo PHP_EOL;

0 commit comments

Comments
 (0)