Skip to content

Commit

Permalink
feat: add experimental offline mode (#183)
Browse files Browse the repository at this point in the history
Resolves #81

~This is based off a lot of the core of the detector - it's not working
yet because I need to figure how to handle passing in the queries to the
local db given that the detector takes `PackageDetails`, but really the
key thing there is how to handle PURL which comes from SBOMs that I
don't really know how to use 😅 (idk if I'm just dumb or what, but for
some reason I've still not been able to figure how to accurately
generate one from a `Gemfile.lock`, `package-lock.json`, etc)~

~If someone could provide some sample SBOMs that would be very useful
(I'll also do a PR adding tests using them as fixtures), and also happy
to receive feedback on the general approach - there are some smaller
bits to discuss, like if fields should be omitted from the JSON output
vs an empty array, and the `Describe` related stuff too.~

This is now working, though personally it feels pretty awkward codewise
- I know I'm bias but I feel like it would be better to trying to bring
across the whole `database` package from the detector, as the API db is
pretty much the same and then you'd have support for zips, directories,
and the API with extra configs like working directories + an extensive
test suite for all three (I don't think it would be as painful as one
might first think, even with `osvscanner` having just been made public
because that's relatively small).

Still, this does work as advertised - there's definitely a few things
that could do with some cleaning up (including if fields should be
omitted from the JSON output vs an empty array, and the `Describe`
related stuff too) but am leaving them for now until I hear what folks
think of the general implementation + my above comment.

I've also gone with two boolean flags rather than the url-based flag
@oliverchang suggested because I didn't feel comfortable trying to
shoehorn that into this PR as well, and now that we're using
`--experimental` it should be fine to completely change these flags in
future.
  • Loading branch information
G-Rath authored Aug 9, 2023
1 parent 777a511 commit 53107dd
Show file tree
Hide file tree
Showing 17 changed files with 2,171 additions and 18 deletions.
18 changes: 17 additions & 1 deletion cmd/osv-scanner/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,19 @@ func run(args []string, stdout, stderr io.Writer) int {
Usage: "also scan files that would be ignored by .gitignore",
Value: false,
},
&cli.BoolFlag{
Name: "experimental-local-db",
Usage: "checks for vulnerabilities using local databases",
},
&cli.BoolFlag{
Name: "experimental-offline",
Usage: "checks for vulnerabilities using local databases that are already cached",
},
&cli.StringFlag{
Name: "experimental-local-db-path",
Usage: "sets the path that local databases should be stored",
Hidden: true,
},
},
ArgsUsage: "[directory1 directory2...]",
Action: func(context *cli.Context) error {
Expand Down Expand Up @@ -149,7 +162,10 @@ func run(args []string, stdout, stderr io.Writer) int {
ConfigOverridePath: context.String("config"),
DirectoryPaths: context.Args().Slice(),
ExperimentalScannerActions: osvscanner.ExperimentalScannerActions{
CallAnalysis: context.Bool("experimental-call-analysis"),
LocalDBPath: context.String("experimental-local-db-path"),
CallAnalysis: context.Bool("experimental-call-analysis"),
CompareLocally: context.Bool("experimental-local-db"),
CompareOffline: context.Bool("experimental-offline"),
},
}, r)

Expand Down
233 changes: 233 additions & 0 deletions cmd/osv-scanner/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ import (
"github.com/go-git/go-git/v5"
)

func createTestDir(t *testing.T) (string, func()) {
t.Helper()

p, err := os.MkdirTemp("", "osv-scanner-test-*")
if err != nil {
t.Fatalf("could not create test directory: %v", err)
}

return p, func() {
_ = os.RemoveAll(p)
}
}

func dedent(t *testing.T, str string) string {
t.Helper()

Expand Down Expand Up @@ -610,6 +623,226 @@ func TestRun_LockfileWithExplicitParseAs(t *testing.T) {
}
}

func TestRun_LocalDatabases(t *testing.T) {
t.Parallel()

tests := []cliTestCase{
// one specific supported lockfile
{
name: "",
args: []string{"", "--experimental-local-db", "./fixtures/locks-many/composer.lock"},
wantExitCode: 0,
wantStdout: `
Scanning dir ./fixtures/locks-many/composer.lock
Scanned %%/fixtures/locks-many/composer.lock file and found 1 package
Loaded Packagist local db from %%/osv-scanner/Packagist/all.zip
No vulnerabilities found
`,
wantStderr: "",
},
// one specific supported sbom with vulns
{
name: "",
args: []string{"", "--experimental-local-db", "--config=./fixtures/osv-scanner-empty-config.toml", "./fixtures/sbom-insecure/postgres-stretch.cdx.xml"},
wantExitCode: 1,
wantStdout: `
Scanning dir ./fixtures/sbom-insecure/postgres-stretch.cdx.xml
Scanned %%/fixtures/sbom-insecure/postgres-stretch.cdx.xml as CycloneDX SBOM and found 136 packages
Loaded Debian local db from %%/osv-scanner/Debian/all.zip
Loaded Go local db from %%/osv-scanner/Go/all.zip
Loaded OSS-Fuzz local db from %%/osv-scanner/OSS-Fuzz/all.zip
+-------------------------------------+------+-----------+--------------------------------+------------------------------------+-------------------------------------------------+
| OSV URL | CVSS | ECOSYSTEM | PACKAGE | VERSION | SOURCE |
+-------------------------------------+------+-----------+--------------------------------+------------------------------------+-------------------------------------------------+
| https://osv.dev/GHSA-f3fp-gc8g-vw66 | 5.9 | Go | github.com/opencontainers/runc | v1.0.1 | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
| https://osv.dev/GHSA-g2j6-57v7-gm8c | 6.1 | Go | github.com/opencontainers/runc | v1.0.1 | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
| https://osv.dev/GHSA-m8cg-xc2p-r3fc | 2.5 | Go | github.com/opencontainers/runc | v1.0.1 | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
| https://osv.dev/GHSA-v95c-p5hm-xq8f | 6 | Go | github.com/opencontainers/runc | v1.0.1 | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
| https://osv.dev/GHSA-vpvm-3wq2-2wvm | 7 | Go | github.com/opencontainers/runc | v1.0.1 | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
| https://osv.dev/GHSA-p782-xgp4-8hr8 | 5.3 | Go | golang.org/x/sys | v0.0.0-20210817142637-7d9622a276b7 | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
+-------------------------------------+------+-----------+--------------------------------+------------------------------------+-------------------------------------------------+
`,
wantStderr: "",
},
// one specific unsupported lockfile
{
name: "",
args: []string{"", "--experimental-local-db", "./fixtures/locks-many/not-a-lockfile.toml"},
wantExitCode: 128,
wantStdout: `
Scanning dir ./fixtures/locks-many/not-a-lockfile.toml
`,
wantStderr: `
No package sources found, --help for usage information.
`,
},
// all supported lockfiles in the directory should be checked
{
name: "",
args: []string{"", "--experimental-local-db", "./fixtures/locks-many"},
wantExitCode: 0,
wantStdout: `
Scanning dir ./fixtures/locks-many
Scanned %%/fixtures/locks-many/Gemfile.lock file and found 1 package
Scanned %%/fixtures/locks-many/alpine.cdx.xml as CycloneDX SBOM and found 15 packages
Scanned %%/fixtures/locks-many/composer.lock file and found 1 package
Scanned %%/fixtures/locks-many/package-lock.json file and found 1 package
Scanned %%/fixtures/locks-many/yarn.lock file and found 1 package
Loaded RubyGems local db from %%/osv-scanner/RubyGems/all.zip
Loaded Alpine local db from %%/osv-scanner/Alpine/all.zip
Loaded Packagist local db from %%/osv-scanner/Packagist/all.zip
Loaded npm local db from %%/osv-scanner/npm/all.zip
Loaded filter from: %%/fixtures/locks-many/osv-scanner.toml
GHSA-whgm-jr23-g3j9 has been filtered out because: Test manifest file
Filtered 1 vulnerability from output
No vulnerabilities found
`,
wantStderr: "",
},
// all supported lockfiles in the directory should be checked
{
name: "",
args: []string{"", "--experimental-local-db", "./fixtures/locks-many-with-invalid"},
wantExitCode: 127,
wantStdout: `
Scanning dir ./fixtures/locks-many-with-invalid
Scanned %%/fixtures/locks-many-with-invalid/Gemfile.lock file and found 1 package
Scanned %%/fixtures/locks-many-with-invalid/yarn.lock file and found 1 package
Loaded RubyGems local db from %%/osv-scanner/RubyGems/all.zip
Loaded npm local db from %%/osv-scanner/npm/all.zip
`,
wantStderr: `
Attempted to scan lockfile but failed: %%/fixtures/locks-many-with-invalid/composer.lock
`,
},
// only the files in the given directories are checked by default (no recursion)
{
name: "",
args: []string{"", "--experimental-local-db", "./fixtures/locks-one-with-nested"},
wantExitCode: 0,
wantStdout: `
Scanning dir ./fixtures/locks-one-with-nested
Scanned %%/fixtures/locks-one-with-nested/yarn.lock file and found 1 package
Loaded npm local db from %%/osv-scanner/npm/all.zip
No vulnerabilities found
`,
wantStderr: "",
},
// nested directories are checked when `--recursive` is passed
{
name: "",
args: []string{"", "--experimental-local-db", "--recursive", "./fixtures/locks-one-with-nested"},
wantExitCode: 0,
wantStdout: `
Scanning dir ./fixtures/locks-one-with-nested
Scanned %%/fixtures/locks-one-with-nested/nested/composer.lock file and found 1 package
Scanned %%/fixtures/locks-one-with-nested/yarn.lock file and found 1 package
Loaded Packagist local db from %%/osv-scanner/Packagist/all.zip
Loaded npm local db from %%/osv-scanner/npm/all.zip
No vulnerabilities found
`,
wantStderr: "",
},
// .gitignored files
{
name: "",
args: []string{"", "--experimental-local-db", "--recursive", "./fixtures/locks-gitignore"},
wantExitCode: 0,
wantStdout: `
Scanning dir ./fixtures/locks-gitignore
Scanned %%/fixtures/locks-gitignore/Gemfile.lock file and found 1 package
Scanned %%/fixtures/locks-gitignore/subdir/yarn.lock file and found 1 package
Loaded RubyGems local db from %%/osv-scanner/RubyGems/all.zip
Loaded npm local db from %%/osv-scanner/npm/all.zip
No vulnerabilities found
`,
wantStderr: "",
},
// ignoring .gitignore
{
name: "",
args: []string{"", "--experimental-local-db", "--recursive", "--no-ignore", "./fixtures/locks-gitignore"},
wantExitCode: 0,
wantStdout: `
Scanning dir ./fixtures/locks-gitignore
Scanned %%/fixtures/locks-gitignore/Gemfile.lock file and found 1 package
Scanned %%/fixtures/locks-gitignore/composer.lock file and found 1 package
Scanned %%/fixtures/locks-gitignore/ignored/Gemfile.lock file and found 1 package
Scanned %%/fixtures/locks-gitignore/ignored/yarn.lock file and found 1 package
Scanned %%/fixtures/locks-gitignore/subdir/Gemfile.lock file and found 1 package
Scanned %%/fixtures/locks-gitignore/subdir/composer.lock file and found 1 package
Scanned %%/fixtures/locks-gitignore/subdir/yarn.lock file and found 1 package
Scanned %%/fixtures/locks-gitignore/yarn.lock file and found 1 package
Loaded RubyGems local db from %%/osv-scanner/RubyGems/all.zip
Loaded Packagist local db from %%/osv-scanner/Packagist/all.zip
Loaded npm local db from %%/osv-scanner/npm/all.zip
No vulnerabilities found
`,
wantStderr: "",
},
// output with json
{
name: "",
args: []string{"", "--experimental-local-db", "--json", "./fixtures/locks-many/composer.lock"},
wantExitCode: 0,
wantStdout: `
{
"results": []
}
`,
wantStderr: `
Scanning dir ./fixtures/locks-many/composer.lock
Scanned %%/fixtures/locks-many/composer.lock file and found 1 package
Loaded Packagist local db from %%/osv-scanner/Packagist/all.zip
`,
},
{
name: "",
args: []string{"", "--experimental-local-db", "--format", "json", "./fixtures/locks-many/composer.lock"},
wantExitCode: 0,
wantStdout: `
{
"results": []
}
`,
wantStderr: `
Scanning dir ./fixtures/locks-many/composer.lock
Scanned %%/fixtures/locks-many/composer.lock file and found 1 package
Loaded Packagist local db from %%/osv-scanner/Packagist/all.zip
`,
},
// output format: markdown table
{
name: "",
args: []string{"", "--experimental-local-db", "--format", "markdown", "./fixtures/locks-many/composer.lock"},
wantExitCode: 0,
wantStdout: `
Scanning dir ./fixtures/locks-many/composer.lock
Scanned %%/fixtures/locks-many/composer.lock file and found 1 package
Loaded Packagist local db from %%/osv-scanner/Packagist/all.zip
No vulnerabilities found
`,
wantStderr: "",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

testDir, cleanupTestDir := createTestDir(t)
defer cleanupTestDir()

old := tt.args

tt.args = []string{"", "--experimental-local-db-path", testDir}
tt.args = append(tt.args, old[1:]...)

testCli(t, tt)
})
}
}

func TestMain(m *testing.M) {
// ensure a git repository doesn't already exist in the fixtures directory,
// in case we didn't get a chance to clean-up properly in the last run
Expand Down
Loading

0 comments on commit 53107dd

Please sign in to comment.