Skip to content

Commit 9fb6c88

Browse files
authored
feat: add support for Drupal advisory database (#372)
The [Drupal Advisory Database](https://github.com/DrupalSecurityTeam/drupal-advisory-database) is a database maintained by Ackama on behalf of the Drupal community that provides [Drupal security advisories](https://www.drupal.org/drupal-security-team/security-advisory-process-and-permissions-policy) in OSV format to allow them to be ingested by osv.dev. While Drupal packages are installed and managed using Composer, they are (mostly) sourced from a dedicated repository rather than the Packagist repository; since these packages are all within the `drupal/` namespace which is owned by the Drupal community, it's been agreed that it is fine to still use the Packagist rather than introduce a new one. This means that existing tools like `osv-scanner` and libraries like `osv-scalibr` should "just work" with the advisories in this database. For the database prefix, the community are decided to use `DRUPAL` as that is straightforward and matches what other has been proposed in other tools like [`dependency-track`](DependencyTrack/dependency-track#4515), which replaces the "SA" prefix used by advisories published on drupal.org so advisory ids can be easily mapped to their original advisory by just replacing `DRUPAL` with `SA`. Discussion on this can be found [here](https://www.drupal.org/project/drupalorg/issues/3410338#comment-16200707) and [here](https://www.drupal.org/project/drupalorg/issues/3410338#comment-16200707) --------- Signed-off-by: Gareth Jones <3151613+G-Rath@users.noreply.github.com>
1 parent 7bd2e88 commit 9fb6c88

7 files changed

Lines changed: 162 additions & 3 deletions

File tree

docs/schema.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,17 @@ The defined database prefixes and their "home" databases are:
267267
</ul>
268268
</td>
269269
</tr>
270+
<tr>
271+
<td><code>DRUPAL</code></td>
272+
<td><a href="https://github.com/DrupalSecurityTeam/drupal-advisory-database">Drupal Advisory Database</a></td>
273+
<td>
274+
<ul>
275+
<li>How to contribute: <a href="https://www.drupal.org/docs/develop/issues/issue-procedures-and-etiquette/reporting-a-security-issue">https://www.drupal.org/docs/develop/issues/issue-procedures-and-etiquette/reporting-a-security-issue</a></li>
276+
<li>Source URL: <code>https://www.drupal.org/security/</code></li>
277+
<li>OSV Formatted URL: <code>https://github.com/DrupalSecurityTeam/drupal-advisory-database/blob/main/advisories/&gt;module&lt;/&gt;ID&lt;.json</code></li>
278+
</ul>
279+
</td>
280+
</tr>
270281
<tr>
271282
<td><code>DSA</code>/<code>DLA</code>/<code>DTSA</code></td>
272283
<td><a href="https://www.debian.org/security/">Debian Security Advisory Database (provided by OSV.dev)</a></td>

tools/osv-linter/internal/checks/schema_generated.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@
387387
"type": "string",
388388
"title": "Currently supported home database identifier prefixes",
389389
"description": "These home databases are also documented at https://ossf.github.io/osv-schema/#id-modified-fields",
390-
"pattern": "^(ASB-A|PUB-A|ALPINE|ALSA|ALBA|ALEA|BELL|BIT|CGA|CURL|CVE|DEBIAN|DSA|DLA|ELA|DTSA|ECHO|EEF|GHSA|GO|GSD|HSEC|JLSEC|KUBE|LBSEC|LSN|MAL|MINI|MGASA|OESA|OSV|openSUSE-SU|PHSA|PSF|PYSEC|RHBA|RHEA|RHSA|RLSA|RXSA|RSEC|RUSTSEC|SUSE-[SRFO]U|UBUNTU|USN|V8)-"
390+
"pattern": "^(ASB-A|PUB-A|ALPINE|ALSA|ALBA|ALEA|BELL|BIT|CGA|CURL|CVE|DEBIAN|DRUPAL|DSA|DLA|ELA|DTSA|ECHO|EEF|GHSA|GO|GSD|HSEC|JLSEC|KUBE|LBSEC|LSN|MAL|MINI|MGASA|OESA|OSV|openSUSE-SU|PHSA|PSF|PYSEC|RHBA|RHEA|RHSA|RLSA|RXSA|RSEC|RUSTSEC|SUSE-[SRFO]U|UBUNTU|USN|V8)-"
391391
},
392392
"severity": {
393393
"type": [

tools/osv-linter/internal/pkgchecker/ecosystems_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,96 @@ func Test_versionsExistInPackagist(t *testing.T) {
550550
}
551551
}
552552

553+
func Test_versionsExistInPackagist_Drupal(t *testing.T) {
554+
t.Parallel()
555+
556+
type args struct {
557+
pkg string
558+
versions []string
559+
}
560+
tests := []struct {
561+
name string
562+
args args
563+
wantErr bool
564+
}{
565+
{
566+
name: "multiple_versions_which_all_exist_on_packagist_repo",
567+
args: args{
568+
pkg: "drupal/core-recommended",
569+
versions: []string{"8.0.3", "10.1.0-rc1", "11.2.0"},
570+
},
571+
wantErr: false,
572+
},
573+
{
574+
name: "multiple_versions_which_all_exist_on_drupal_repo",
575+
args: args{
576+
pkg: "drupal/simple_sitemap",
577+
versions: []string{"1.3.0", "3.0.0-rc3", "4.1.3"},
578+
},
579+
wantErr: false,
580+
},
581+
{
582+
// the drupal repo has advisories for this package, but not versions
583+
name: "multiple_versions_which_all_exist_on_both_repos",
584+
args: args{
585+
pkg: "drupal/core",
586+
versions: []string{"8.0.1", "9.4.0-beta1", "11.0.2"},
587+
},
588+
wantErr: false,
589+
},
590+
{
591+
name: "multiple_versions_with_one_that_does_not_exist_on_packagist_repo",
592+
args: args{
593+
pkg: "drupal/core-recommended",
594+
versions: []string{"8.9.14", "9.5.0-rc3", "10.5.1"},
595+
},
596+
wantErr: true,
597+
},
598+
{
599+
name: "multiple_versions_with_one_that_does_not_exist_on_drupal_repo",
600+
args: args{
601+
pkg: "drupal/simple_sitemap",
602+
versions: []string{"1.1.0", "3.0.0-rc5", "4.1.1"},
603+
},
604+
wantErr: true,
605+
},
606+
{
607+
// the drupal repo has advisories for this package, but not versions
608+
name: "multiple_versions_with_one_that_does_not_exist_on_both_repos",
609+
args: args{
610+
pkg: "drupal/core",
611+
versions: []string{"8.7.5", "10.1.9", "11.1.4"},
612+
},
613+
wantErr: true,
614+
},
615+
{
616+
name: "an_invalid_version",
617+
args: args{
618+
pkg: "drupal/simple_sitemap",
619+
versions: []string{"!"},
620+
},
621+
wantErr: true,
622+
},
623+
{
624+
name: "a_package_that_does_not_exit",
625+
args: args{
626+
pkg: "drupal/not-a-real-package",
627+
versions: []string{"1.0.0"},
628+
},
629+
wantErr: true,
630+
},
631+
}
632+
for _, tt := range tests {
633+
t.Run(tt.name, func(t *testing.T) {
634+
t.Parallel()
635+
636+
if err := versionsExistInPackagist(tt.args.pkg, tt.args.versions); (err != nil) != tt.wantErr {
637+
t.Errorf("versionsExistInPackagist() error = %v, wantErr %v", err, tt.wantErr)
638+
}
639+
})
640+
}
641+
}
642+
553643
func Test_versionsExistInPub(t *testing.T) {
554644
t.Parallel()
555645

tools/osv-linter/internal/pkgchecker/package_check.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,15 @@ func existsInNuget(pkg string) bool {
127127

128128
// Validate the existence of a package in Packagist.
129129
func existsInPackagist(pkg string) bool {
130+
// most drupal packages are published in a dedicated repository, so check there first
131+
if strings.HasPrefix(pkg, "drupal/") {
132+
drupalInstanceURL := fmt.Sprintf("%s/%s.json", "https://packages.drupal.org/files/packages/8/p2", pkg)
133+
134+
if checkPackageExists(drupalInstanceURL) {
135+
return true
136+
}
137+
}
138+
130139
packageInstanceURL := fmt.Sprintf("%s/%s.json", EcosystemBaseURLs["Packagist"], pkg)
131140

132141
return checkPackageExists(packageInstanceURL)

tools/osv-linter/internal/pkgchecker/package_check_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,29 @@ func Test_existsInPackagist(t *testing.T) {
152152
}{
153153
{
154154
name: "existing package",
155+
pkg: "composer/installers",
156+
want: true,
157+
},
158+
{
159+
name: "existing drupal packagist package",
155160
pkg: "drupal/core",
156161
want: true,
157162
},
163+
{
164+
name: "existing drupal repo package",
165+
pkg: "drupal/simple_sitemap",
166+
want: true,
167+
},
158168
{
159169
name: "non-existing package",
160170
pkg: "non-existing-package",
161171
want: false,
162172
},
173+
{
174+
name: "non-existing drupal package",
175+
pkg: "drupal/non-existing-package",
176+
want: false,
177+
},
163178
}
164179
for _, tt := range tests {
165180
t.Run(tt.name, func(t *testing.T) {

tools/osv-linter/internal/pkgchecker/version_check.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package pkgchecker
22

33
import (
4+
"errors"
45
"fmt"
56
"io"
67
"net/http"
@@ -15,6 +16,8 @@ import (
1516
"golang.org/x/mod/semver"
1617
)
1718

19+
var errPathNotFound = errors.New("path not found")
20+
1821
func fetchPackageData(packageInstanceURL string) ([]byte, error) {
1922
resp, err := faulttolerant.Get(packageInstanceURL)
2023
if err != nil {
@@ -43,7 +46,14 @@ func versionsExistInGeneric(
4346

4447
// Fetch all known versions of package.
4548
versionsInRepository := []string{}
46-
for _, result := range gjson.GetBytes(respJSON, versionsPath).Array() {
49+
50+
r := gjson.GetBytes(respJSON, versionsPath)
51+
52+
if !r.Exists() {
53+
return errPathNotFound
54+
}
55+
56+
for _, result := range r.Array() {
4757
versionsInRepository = append(versionsInRepository, result.String())
4858
}
4959
// Determine which referenced versions are missing.
@@ -278,6 +288,30 @@ func versionsExistInJulia(pkg string, versions []string) error {
278288

279289
// Confirm that all specified versions of a package exist in Packagist.
280290
func versionsExistInPackagist(pkg string, versions []string) error {
291+
// most drupal packages are published in a dedicated repository, so check there first
292+
if strings.HasPrefix(pkg, "drupal/") {
293+
drupalInstanceURL := fmt.Sprintf("%s/%s.json", "https://packages.drupal.org/files/packages/8/p2", pkg)
294+
295+
err := versionsExistInGeneric(
296+
pkg, versions,
297+
"Packagist",
298+
drupalInstanceURL,
299+
fmt.Sprintf("packages.%s.#.version", pkg),
300+
)
301+
302+
if err == nil {
303+
return err
304+
}
305+
306+
// not all drupal packages are published on the drupal repository,
307+
// and some packages are only present with security advisories
308+
//
309+
// todo: ideally we should not be checking the error message itself
310+
if !strings.HasSuffix(err.Error(), "bad response: 404") && !errors.Is(err, errPathNotFound) {
311+
return err
312+
}
313+
}
314+
281315
packageInstanceURL := fmt.Sprintf("%s/%s.json", EcosystemBaseURLs["Packagist"], pkg)
282316

283317
return versionsExistInGeneric(

validation/schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@
387387
"type": "string",
388388
"title": "Currently supported home database identifier prefixes",
389389
"description": "These home databases are also documented at https://ossf.github.io/osv-schema/#id-modified-fields",
390-
"pattern": "^(ASB-A|PUB-A|ALPINE|ALSA|ALBA|ALEA|BELL|BIT|CGA|CURL|CVE|DEBIAN|DSA|DLA|ELA|DTSA|ECHO|EEF|GHSA|GO|GSD|HSEC|JLSEC|KUBE|LBSEC|LSN|MAL|MINI|MGASA|OESA|OSV|openSUSE-SU|PHSA|PSF|PYSEC|RHBA|RHEA|RHSA|RLSA|RXSA|RSEC|RUSTSEC|SUSE-[SRFO]U|UBUNTU|USN|V8)-"
390+
"pattern": "^(ASB-A|PUB-A|ALPINE|ALSA|ALBA|ALEA|BELL|BIT|CGA|CURL|CVE|DEBIAN|DRUPAL|DSA|DLA|ELA|DTSA|ECHO|EEF|GHSA|GO|GSD|HSEC|JLSEC|KUBE|LBSEC|LSN|MAL|MINI|MGASA|OESA|OSV|openSUSE-SU|PHSA|PSF|PYSEC|RHBA|RHEA|RHSA|RLSA|RXSA|RSEC|RUSTSEC|SUSE-[SRFO]U|UBUNTU|USN|V8)-"
391391
},
392392
"severity": {
393393
"type": [

0 commit comments

Comments
 (0)