From ec1894243527354e936ab1b4b4127347aaea43f6 Mon Sep 17 00:00:00 2001 From: Rex P <106129829+another-rex@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:26:31 +1000 Subject: [PATCH] Recreated Github Action PR (#432) This PR features: - Refactors the format flag's internal logic so that we can don't need to repeat the format types so much, and we can test when we add a new format entry if we forgot anything. - Adds a new format "sarif", which returns a SARIF report (closes #216 ) - Adds a Github Action `action.yaml` and it's specialized dockerfile `action.dockerfile`. This docker image runs a bash script wrapping osv-scanner, first by preprocessing the input so the last argument will be split by new line, allowing the workflow user to pass in multiple directories/files they wish to scan. The script also changes exit codes 127 and 128 to 0 as they contain errors that the user can't really do anything about. - Adds two reusable workflows using this new github action for this repo - Reusable PR workflow, for using to check if PRs introduce new vulnerabilities. - Reusable Scheduled workflow, for use to regularly check for new vulns applying to your existing vulns. - Adds an experimental flag: `--experimental-diff`, which will only output the difference between a previous run and this run of the osv-scanner. This is for use in the PR workflow. - Sorts the grouped ID output. Closes #57 Currently the reusable workflow has to point to a specific action which cannot be relative (otherwise it would point to the wrong action when reused in another repo). This means right now it's pointed to this fork/branch instead of the master branch, this will need to be updated once this PR is merged. Example of what workflow sarif output looks like: ![image](https://github.com/google/osv-scanner/assets/106129829/fc7a0ac4-f3d8-4524-93ba-7b03dd0313cd) Here is an example of the PR reusable workflow working: https://github.com/another-rex/scorecard-check-osv-e2e/pull/1 That PR adds an additional vulnerability, which causes it to fail. You can see that only the new vuln is showing up in the code scanning report: https://github.com/another-rex/scorecard-check-osv-e2e/security/code-scanning/1 TODO after this PR is merged: - Change links that point to this PR branch to point to main (and/or a tagged commit of main) - Add support for annotations - Add documentation (this is for later, as we want to dogfood it in our own repos first before broadcasting this widely) --------- Signed-off-by: Rex P --- .github/workflows/osv-scanner-pr.yml | 29 ++ .github/workflows/osv-scanner-reusable-pr.yml | 82 ++++ .../osv-scanner-reusable-scheduled.yml | 47 ++ .github/workflows/osv-scanner-scheduled.yml | 28 ++ action.dockerfile | 44 ++ action.yml | 21 - actions/diff/action.yml | 25 ++ actions/scanner/action.yml | 26 ++ cmd/osv-diff/main.go | 169 ++++++++ .../fixtures/locks-many/osv-scanner.toml | 4 + .../fixtures/locks-many/package-lock.json | 9 + .../fixtures/osv-scanner-empty-config.toml | 1 + cmd/osv-scanner/main.go | 50 ++- cmd/osv-scanner/main_test.go | 101 ++++- exit_code_redirect.sh | 50 +++ go.mod | 1 + go.sum | 17 + .../ci/fixtures/vulns/test-vuln-diff-a-b.json | 122 ++++++ .../ci/fixtures/vulns/test-vuln-diff-b-a.json | 3 + .../ci/fixtures/vulns/test-vuln-diff-b-c.json | 3 + .../ci/fixtures/vulns/test-vuln-diff-c-b.json | 329 ++++++++++++++ .../fixtures/vulns/test-vuln-results-a.json | 292 +++++++++++++ .../fixtures/vulns/test-vuln-results-b.json | 387 +++++++++++++++++ .../fixtures/vulns/test-vuln-results-c.json | 85 ++++ internal/ci/utility.go | 23 + internal/ci/vulnerability_result_diff.go | 67 +++ internal/ci/vulnerability_result_diff_test.go | 63 +++ internal/output/fixtures/flattened_vulns.json | 403 ++++++++++++++++++ .../fixtures/group_fixed_version_output.json | 8 + internal/output/sarif.go | 123 ++++++ internal/output/sarif_test.go | 37 ++ internal/output/table.go | 15 +- pkg/grouper/grouper.go | 2 + pkg/grouper/grouper_test.go | 4 +- pkg/models/results.go | 26 ++ pkg/reporter/format.go | 29 ++ pkg/reporter/format_test.go | 22 + pkg/reporter/sarif_reporter.go | 40 ++ pkg/reporter/table_reporter.go | 7 +- 39 files changed, 2742 insertions(+), 52 deletions(-) create mode 100644 .github/workflows/osv-scanner-pr.yml create mode 100644 .github/workflows/osv-scanner-reusable-pr.yml create mode 100644 .github/workflows/osv-scanner-reusable-scheduled.yml create mode 100644 .github/workflows/osv-scanner-scheduled.yml create mode 100644 action.dockerfile delete mode 100644 action.yml create mode 100644 actions/diff/action.yml create mode 100644 actions/scanner/action.yml create mode 100644 cmd/osv-diff/main.go create mode 100644 cmd/osv-scanner/fixtures/locks-many/osv-scanner.toml create mode 100644 cmd/osv-scanner/fixtures/locks-many/package-lock.json create mode 100755 exit_code_redirect.sh create mode 100644 internal/ci/fixtures/vulns/test-vuln-diff-a-b.json create mode 100644 internal/ci/fixtures/vulns/test-vuln-diff-b-a.json create mode 100644 internal/ci/fixtures/vulns/test-vuln-diff-b-c.json create mode 100644 internal/ci/fixtures/vulns/test-vuln-diff-c-b.json create mode 100644 internal/ci/fixtures/vulns/test-vuln-results-a.json create mode 100644 internal/ci/fixtures/vulns/test-vuln-results-b.json create mode 100644 internal/ci/fixtures/vulns/test-vuln-results-c.json create mode 100644 internal/ci/utility.go create mode 100644 internal/ci/vulnerability_result_diff.go create mode 100644 internal/ci/vulnerability_result_diff_test.go create mode 100644 internal/output/fixtures/flattened_vulns.json create mode 100644 internal/output/fixtures/group_fixed_version_output.json create mode 100644 internal/output/sarif.go create mode 100644 internal/output/sarif_test.go create mode 100644 pkg/reporter/format.go create mode 100644 pkg/reporter/format_test.go create mode 100644 pkg/reporter/sarif_reporter.go diff --git a/.github/workflows/osv-scanner-pr.yml b/.github/workflows/osv-scanner-pr.yml new file mode 100644 index 0000000000..93e575f8b0 --- /dev/null +++ b/.github/workflows/osv-scanner-pr.yml @@ -0,0 +1,29 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: osv-scanner + +on: + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + merge_group: + branches: [ main ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + scan-pr-attempt: + uses: "./.github/workflows/osv-scanner-reusable-pr.yml" diff --git a/.github/workflows/osv-scanner-reusable-pr.yml b/.github/workflows/osv-scanner-reusable-pr.yml new file mode 100644 index 0000000000..e6ee701c3e --- /dev/null +++ b/.github/workflows/osv-scanner-reusable-pr.yml @@ -0,0 +1,82 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: OSV-Scanner PR scanning + +on: + workflow_call: + +jobs: + scan-pr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + # Do persist credentials, as we need it for the git checkout later + - name: "Checkout target branch" + run: git checkout $GITHUB_BASE_REF + - name: "Run scanner on existing code" + uses: another-rex/osv-scanner/actions/scanner@markdown-output + continue-on-error: true + with: + results-format: json + results-file: old-results.json + to-scan: . + - name: "Checkout current branch" + run: git checkout $GITHUB_SHA + - name: "Run scanner on new code" + uses: another-rex/osv-scanner/actions/scanner@markdown-output + with: + results-format: json + results-file: new-results.json + to-scan: . + continue-on-error: true + - name: "Run osv-diff" + uses: another-rex/osv-scanner/actions/diff@markdown-output + with: + results-format: sarif + output-file: final-results.sarif + old-results: old-results.json + new-results: new-results.json + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + if: '!cancelled()' + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + with: + name: SARIF file + path: final-results.sarif + retention-days: 5 + - name: "Upload old scan json results" + if: '!cancelled()' + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + with: + name: old-json-results + path: old-results.json + retention-days: 5 + - name: "Upload new scan json results" + if: '!cancelled()' + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + with: + name: new-json-results + path: new-results.json + retention-days: 5 + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + if: '!cancelled()' + uses: github/codeql-action/upload-sarif@6c089f53dd51dc3fc7e599c3cb5356453a52ca9e # v2.20.0 + with: + sarif_file: final-results.sarif + diff --git a/.github/workflows/osv-scanner-reusable-scheduled.yml b/.github/workflows/osv-scanner-reusable-scheduled.yml new file mode 100644 index 0000000000..26a4d4ff8e --- /dev/null +++ b/.github/workflows/osv-scanner-reusable-scheduled.yml @@ -0,0 +1,47 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: OSV-Scanner PR scanning + +on: + workflow_call: + +jobs: + scan-pr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: "Run scanner" + uses: another-rex/osv-scanner/actions/scanner@markdown-output + with: + results-format: sarif + results-file: results.sarif + to-scan: . + recursive-scan: true + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + if: '!cancelled()' + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + if: '!cancelled()' + uses: github/codeql-action/upload-sarif@6c089f53dd51dc3fc7e599c3cb5356453a52ca9e # v2.20.0 + with: + sarif_file: results.sarif + diff --git a/.github/workflows/osv-scanner-scheduled.yml b/.github/workflows/osv-scanner-scheduled.yml new file mode 100644 index 0000000000..60e77d594e --- /dev/null +++ b/.github/workflows/osv-scanner-scheduled.yml @@ -0,0 +1,28 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: osv-scanner + +on: + schedule: + - cron: '12 12 * * 1' + push: + branches: [ "main" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + scan-pr-attempt: + uses: "./.github/workflows/osv-scanner-reusable-scheduled.yml" diff --git a/action.dockerfile b/action.dockerfile new file mode 100644 index 0000000000..1bd5dec03a --- /dev/null +++ b/action.dockerfile @@ -0,0 +1,44 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM golang:alpine@sha256:fd9d9d7194ec40a9a6ae89fcaef3e47c47de7746dd5848ab5343695dbbd09f8c + +RUN mkdir /src +WORKDIR /src + +COPY ./go.mod /src/go.mod +COPY ./go.sum /src/go.sum +RUN go mod download + +COPY ./ /src/ +RUN go build -o osv-scanner ./cmd/osv-scanner/ +RUN go build -o osv-diff ./cmd/osv-diff/ + +FROM alpine:3.17@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126 +RUN apk --no-cache add \ + ca-certificates \ + git \ + bash + +# Allow git to run on mounted directories +RUN git config --global --add safe.directory '*' + +WORKDIR /root/ +COPY --from=0 /src/osv-scanner ./ +COPY --from=0 /src/osv-diff ./ +COPY ./exit_code_redirect.sh ./ + +ENV PATH="${PATH}:/root" + +ENTRYPOINT ["bash", "/root/exit_code_redirect.sh"] diff --git a/action.yml b/action.yml deleted file mode 100644 index 5d8ded885a..0000000000 --- a/action.yml +++ /dev/null @@ -1,21 +0,0 @@ -# Currently experimental. -name: 'osv-scanner' -description: 'Scans your directory against the OSV database (Experimental)' -inputs: - to-scan: - description: 'Directory to scan' - required: true - default: '/github/workspace' - version: - description: 'osv-scanner version to use' - required: true - arch: - description: 'osv-scanner architecture' - required: false - default: 'amd64' -runs: - using: 'docker' - image: 'ghcr.io/google/osv-scanner:${{ inputs.version }}-${{ inputs.arch }}' - args: - - '--skip-git' - - ${{ inputs.to-scan }} diff --git a/actions/diff/action.yml b/actions/diff/action.yml new file mode 100644 index 0000000000..563a446257 --- /dev/null +++ b/actions/diff/action.yml @@ -0,0 +1,25 @@ +# Currently experimental. +name: 'osv-scanner-diff' +description: 'Finds the difference between two osv-scanner json results' +inputs: + output-file: + description: 'Output path' + required: true + results-format: + description: 'Output result format' + default: 'sarif' + old-results: + description: 'Old results to get the difference against' + required: true + new-results: + description: 'New results to get the difference against' + required: true +runs: + using: 'docker' + image: '../../action.dockerfile' + entrypoint: /root/osv-diff + args: + - '--output=${{ inputs.output-file }}' + - '--format=${{ inputs.results-format }}' + - '--old=${{ inputs.old-results }}' + - '--new=${{ inputs.new-results }}' diff --git a/actions/scanner/action.yml b/actions/scanner/action.yml new file mode 100644 index 0000000000..1c269426ae --- /dev/null +++ b/actions/scanner/action.yml @@ -0,0 +1,26 @@ +# Currently experimental. +name: 'osv-scanner' +description: 'Scans your directory against the OSV database (Experimental)' +inputs: + to-scan: + description: 'Directories to scan' + default: "./" + results-file: + description: 'Output path' + required: true + results-format: + description: 'Output result format' + default: 'sarif' + recursive-scan: + description: 'Recursively scan though subdirectories' + required: false + default: true +runs: + using: 'docker' + image: '../../action.dockerfile' + args: + - '--skip-git' + - '--output=${{ inputs.results-file }}' + - '--format=${{ inputs.results-format }}' + - '--recursive=${{ inputs.recursive-scan }}' + - ${{ inputs.to-scan }} diff --git a/cmd/osv-diff/main.go b/cmd/osv-diff/main.go new file mode 100644 index 0000000000..7d55135923 --- /dev/null +++ b/cmd/osv-diff/main.go @@ -0,0 +1,169 @@ +package main + +import ( + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/google/osv-scanner/internal/ci" + "github.com/google/osv-scanner/pkg/osvscanner" + "github.com/google/osv-scanner/pkg/reporter" + "github.com/urfave/cli/v2" + "golang.org/x/exp/slices" + "golang.org/x/term" +) + +var ( + // Update this variable when doing a release + version = "1.3.5" + commit = "n/a" + date = "n/a" +) + +func run(args []string, stdout, stderr io.Writer) int { + var r reporter.Reporter + + cli.VersionPrinter = func(ctx *cli.Context) { + // Use the app Writer and ErrWriter since they will be the writers to keep parallel tests consistent + r = reporter.NewTableReporter(ctx.App.Writer, ctx.App.ErrWriter, false, 0) + r.PrintText(fmt.Sprintf("osv-scanner version: %s\ncommit: %s\nbuilt at: %s\n", ctx.App.Version, commit, date)) + } + + app := &cli.App{ + Name: "osv-scanner-diff", + Version: version, + Usage: "compares the output of multiple osv-scanner runs to find new vulnerabilities", + Description: "Remove vulnerabilities in the old OSV JSON output from the new OSV JSON output", + Suggest: true, + Writer: stdout, + ErrWriter: stderr, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "old", + Usage: "the old osv json output", + TakesFile: true, + Required: true, + }, + &cli.StringFlag{ + Name: "new", + Usage: "the new osv json output", + TakesFile: true, + Required: true, + }, + &cli.StringFlag{ + Name: "format", + Aliases: []string{"f"}, + Usage: "sets the output format", + Value: "table", + Action: func(context *cli.Context, s string) error { + if slices.Contains(reporter.Format(), s) { + return nil + } + + return fmt.Errorf("unsupported output format \"%s\" - must be one of: %s", s, strings.Join(reporter.Format(), ", ")) + }, + }, + &cli.StringFlag{ + Name: "output", + Usage: "saves the result to the given file path", + TakesFile: true, + }, + }, + Action: func(context *cli.Context) error { + format := context.String("format") + + outputPath := context.String("output") + if outputPath != "" { + var err error + stdout, err = os.Create(outputPath) + if err != nil { + return fmt.Errorf("failed to create output file: %w", err) + } + } + + var termWidth int + if outputPath != "" { + var err error + termWidth, _, err = term.GetSize(int(os.Stdout.Fd())) + if err != nil { // If output is not a terminal, + termWidth = 0 + } + } else { // Output is a file + termWidth = 0 + } + + var err error + if r, err = reporter.New(format, stdout, stderr, termWidth); err != nil { + return err + } + + oldPath := context.String("old") + newPath := context.String("new") + + oldVulns, err := ci.LoadVulnResults(oldPath) + if err != nil { + return fmt.Errorf("failed to open old results at %s: %w", oldPath, err) + } + + newVulns, err := ci.LoadVulnResults(newPath) + if err != nil { + return fmt.Errorf("failed to open new results at %s: %w", newPath, err) + } + + diffVulns := ci.DiffVulnerabilityResults(oldVulns, newVulns) + + if errPrint := r.PrintResult(&diffVulns); errPrint != nil { + return fmt.Errorf("failed to write output: %w", errPrint) + } + + // if vulnerability exists it should return error + if len(diffVulns.Results) > 0 { + // If any vulnerabilities are called, then we return VulnerabilitiesFoundErr + for _, vf := range diffVulns.Flatten() { + if vf.GroupInfo.IsCalled() { + return osvscanner.VulnerabilitiesFoundErr + } + } + // Otherwise return OnlyUncalledVulnerabilitiesFoundErr + return osvscanner.OnlyUncalledVulnerabilitiesFoundErr + } + + return nil + }, + } + + if err := app.Run(args); err != nil { + if r == nil { + r = reporter.NewTableReporter(stdout, stderr, false, 0) + } + if errors.Is(err, osvscanner.VulnerabilitiesFoundErr) { + return 1 + } + + if errors.Is(err, osvscanner.OnlyUncalledVulnerabilitiesFoundErr) { + // TODO: Discuss whether to have a different exit code now that running call analysis is not default + return 2 + } + + if errors.Is(err, osvscanner.NoPackagesFoundErr) { + r.PrintError("No package sources found, --help for usage information.\n") + return 128 + } + + r.PrintError(fmt.Sprintf("%v\n", err)) + } + + // if we've been told to print an error, and not already exited with + // a specific error code, then exit with a generic non-zero code + if r != nil && r.HasPrintedError() { + return 127 + } + + return 0 +} + +func main() { + os.Exit(run(os.Args, os.Stdout, os.Stderr)) +} diff --git a/cmd/osv-scanner/fixtures/locks-many/osv-scanner.toml b/cmd/osv-scanner/fixtures/locks-many/osv-scanner.toml new file mode 100644 index 0000000000..5cfb2dc242 --- /dev/null +++ b/cmd/osv-scanner/fixtures/locks-many/osv-scanner.toml @@ -0,0 +1,4 @@ +[[IgnoredVulns]] +id = "GHSA-whgm-jr23-g3j9" +# ignore_until = 2022-11-09 +reason = "Test manifest file" diff --git a/cmd/osv-scanner/fixtures/locks-many/package-lock.json b/cmd/osv-scanner/fixtures/locks-many/package-lock.json new file mode 100644 index 0000000000..e3a2d44973 --- /dev/null +++ b/cmd/osv-scanner/fixtures/locks-many/package-lock.json @@ -0,0 +1,9 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "ansi-html": { + "version": "0.0.1" + } + } +} diff --git a/cmd/osv-scanner/fixtures/osv-scanner-empty-config.toml b/cmd/osv-scanner/fixtures/osv-scanner-empty-config.toml index e69de29bb2..9a13cb6b4c 100644 --- a/cmd/osv-scanner/fixtures/osv-scanner-empty-config.toml +++ b/cmd/osv-scanner/fixtures/osv-scanner-empty-config.toml @@ -0,0 +1 @@ +# An empty config file to override the ignore config diff --git a/cmd/osv-scanner/main.go b/cmd/osv-scanner/main.go index 414f79eb2d..2a9ec4c060 100644 --- a/cmd/osv-scanner/main.go +++ b/cmd/osv-scanner/main.go @@ -5,10 +5,13 @@ import ( "fmt" "io" "os" + "strings" "github.com/google/osv-scanner/pkg/osv" "github.com/google/osv-scanner/pkg/osvscanner" "github.com/google/osv-scanner/pkg/reporter" + "golang.org/x/exp/slices" + "golang.org/x/term" "github.com/urfave/cli/v2" ) @@ -25,7 +28,7 @@ func run(args []string, stdout, stderr io.Writer) int { cli.VersionPrinter = func(ctx *cli.Context) { // Use the app Writer and ErrWriter since they will be the writers to keep parallel tests consistent - r = reporter.NewTableReporter(ctx.App.Writer, ctx.App.ErrWriter, false) + r = reporter.NewTableReporter(ctx.App.Writer, ctx.App.ErrWriter, false, 0) r.PrintText(fmt.Sprintf("osv-scanner version: %s\ncommit: %s\nbuilt at: %s\n", ctx.App.Version, commit, date)) } @@ -68,21 +71,22 @@ func run(args []string, stdout, stderr io.Writer) int { Usage: "sets the output format", Value: "table", Action: func(context *cli.Context, s string) error { - switch s { - case - "table", - "json", //nolint:goconst - "markdown": + if slices.Contains(reporter.Format(), s) { return nil } - return fmt.Errorf("unsupported output format \"%s\" - must be one of: \"table\", \"json\", \"markdown\"", s) + return fmt.Errorf("unsupported output format \"%s\" - must be one of: %s", s, strings.Join(reporter.Format(), ", ")) }, }, &cli.BoolFlag{ Name: "json", Usage: "sets output to json (deprecated, use --format json instead)", }, + &cli.StringFlag{ + Name: "output", + Usage: "saves the result to the given file path", + TakesFile: true, + }, &cli.BoolFlag{ Name: "skip-git", Usage: "skip scanning git repositories", @@ -113,15 +117,26 @@ func run(args []string, stdout, stderr io.Writer) int { format = "json" } - switch format { - case "json": - r = reporter.NewJSONReporter(stdout, stderr) - case "table": - r = reporter.NewTableReporter(stdout, stderr, false) - case "markdown": - r = reporter.NewTableReporter(stdout, stderr, true) - default: - return fmt.Errorf("%v is not a valid format", format) + outputPath := context.String("output") + + termWidth := 0 + var err error + if outputPath != "" { // Output is definitely a file + stdout, err = os.Create(outputPath) + if err != nil { + return fmt.Errorf("failed to create output file: %w", err) + } + } else { // Output might be a terminal + if stdoutAsFile, ok := stdout.(*os.File); ok { + termWidth, _, err = term.GetSize(int(stdoutAsFile.Fd())) + if err != nil { // If output is not a terminal, + termWidth = 0 + } + } + } + + if r, err = reporter.New(format, stdout, stderr, termWidth); err != nil { + return err } vulnResult, err := osvscanner.DoScan(osvscanner.ScannerActions{ @@ -147,13 +162,14 @@ func run(args []string, stdout, stderr io.Writer) int { return fmt.Errorf("failed to write output: %w", errPrint) } + // Could be nil, VulnerabilitiesFoundErr, or OnlyUncalledVulnerabilitiesFoundErr return err }, } if err := app.Run(args); err != nil { if r == nil { - r = reporter.NewTableReporter(stdout, stderr, false) + r = reporter.NewTableReporter(stdout, stderr, false, 0) } if errors.Is(err, osvscanner.VulnerabilitiesFoundErr) { return 1 diff --git a/cmd/osv-scanner/main_test.go b/cmd/osv-scanner/main_test.go index 6b40d84899..a2671d9312 100644 --- a/cmd/osv-scanner/main_test.go +++ b/cmd/osv-scanner/main_test.go @@ -177,7 +177,7 @@ func TestRun(t *testing.T) { }, // all supported lockfiles in the directory should be checked { - name: "", + name: "Scan locks-many", args: []string{"", "./fixtures/locks-many"}, wantExitCode: 0, wantStdout: ` @@ -185,7 +185,11 @@ func TestRun(t *testing.T) { Scanned %%/fixtures/locks-many/Gemfile.lock file and found 1 packages Scanned %%/fixtures/locks-many/alpine.cdx.xml as CycloneDX SBOM and found 15 packages Scanned %%/fixtures/locks-many/composer.lock file and found 1 packages + Scanned %%/fixtures/locks-many/package-lock.json file and found 1 packages Scanned %%/fixtures/locks-many/yarn.lock file and found 1 packages + Loaded filter from: %%/fixtures/locks-many/osv-scanner.toml + GHSA-whgm-jr23-g3j9 has been filtered out because: Test manifest file + Filtered 1 vulnerabilities from output No vulnerabilities found `, wantStderr: "", @@ -290,6 +294,101 @@ func TestRun(t *testing.T) { Scanned %%/fixtures/locks-many/composer.lock file and found 1 packages `, }, + // output format: sarif + { + name: "Empty sarif output", + args: []string{"", "--format", "sarif", "./fixtures/locks-many/composer.lock"}, + wantExitCode: 0, + wantStdout: ` + { + "version": "2.1.0", + "$schema": "https://json.schemastore.org/sarif-2.1.0.json", + "runs": [ + { + "tool": { + "driver": { + "informationUri": "https://github.com/google/osv-scanner", + "name": "osv-scanner", + "rules": [ + { + "id": "vulnerable-packages", + "shortDescription": { + "text": "This manifest file contains one or more vulnerable packages." + } + } + ] + } + }, + "results": [] + } + ] + } + `, + wantStderr: ` + Scanning dir ./fixtures/locks-many/composer.lock + Scanned %%/fixtures/locks-many/composer.lock file and found 1 packages + `, + }, + { + name: "Sarif with vulns", + args: []string{"", "--format", "sarif", "--config", "./fixtures/osv-scanner-empty-config.toml", "./fixtures/locks-many/package-lock.json"}, + wantExitCode: 1, + wantStdout: ` + { + "version": "2.1.0", + "$schema": "https://json.schemastore.org/sarif-2.1.0.json", + "runs": [ + { + "tool": { + "driver": { + "informationUri": "https://github.com/google/osv-scanner", + "name": "osv-scanner", + "rules": [ + { + "id": "vulnerable-packages", + "shortDescription": { + "text": "This manifest file contains one or more vulnerable packages." + } + } + ] + } + }, + "artifacts": [ + { + "location": { + "uri": "fixtures/locks-many/package-lock.json" + }, + "length": -1 + } + ], + "results": [ + { + "ruleId": "vulnerable-packages", + "ruleIndex": 0, + "level": "warning", + "message": { + "text": "+-----------+-------------------------------------+-----------------+---------------+\n| PACKAGE \u0026nbsp; | VULNERABILITY ID \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp;| CURRENT VERSION | FIXED VERSION |\n+-----------+-------------------------------------+-----------------+---------------+\n| ansi-html | https://osv.dev/GHSA-whgm-jr23-g3j9 | 0.0.1 \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; | 0.0.8 \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; |\n+-----------+-------------------------------------+-----------------+---------------+" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "fixtures/locks-many/package-lock.json" + } + } + } + ] + } + ] + } + ] + } + `, + wantStderr: ` + Scanning dir ./fixtures/locks-many/package-lock.json + Scanned %%/fixtures/locks-many/package-lock.json file and found 1 packages + `, + }, // output format: markdown table { name: "", diff --git a/exit_code_redirect.sh b/exit_code_redirect.sh new file mode 100755 index 0000000000..a7b35e0cbd --- /dev/null +++ b/exit_code_redirect.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script works around a limitation of github actions where +# actions cannot receive a variable number of arguments in an array +# This script takes the last argument and splits it out by new line, +# passing it into osv-scanner as separate arguments + +# Get the total number of arguments +total_args=$# + +# Extract the last argument +last_arg="${!total_args}" + +# Remove the last argument from the list +args=${@:1:$((total_args - 1))} + +# () inteprets spaces as spearate entries in an array +# tr replaces newlines with spaces +split_args=($(echo "$last_arg" | tr '\n' ' ')) + +# Execute osv-scanner with the provided arguments +osv-scanner $args "${split_args[@]}" + +# Store the exit code +exit_code=$? + +echo "Exit code: ${exit_code}" +# Check if the exit code is 127 or 128 and modify it to 0 +# - 127: General error, not something the user can fix most of the time +# - 128: No lockfiles found +if [[ $exit_code -eq 127 || $exit_code -eq 128 ]]; then + exit_code=0 +fi + +# Exit with the modified exit code +exit $exit_code \ No newline at end of file diff --git a/go.mod b/go.mod index 7d829a9319..9cd24f35e0 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/jedib0t/go-pretty/v6 v6.4.6 github.com/kr/pretty v0.3.1 + github.com/owenrumney/go-sarif/v2 v2.2.0 github.com/package-url/packageurl-go v0.1.1 github.com/spdx/tools-golang v0.5.2 github.com/urfave/cli/v2 v2.25.7 diff --git a/go.sum b/go.sum index e2fcd538bf..6eab4c1400 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,7 @@ github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= @@ -40,6 +41,9 @@ github.com/goark/go-cvss v1.6.6 h1:WJFuIWqmAw1Ilb9USv0vuX+nYzOWJp8lIujseJ/y3sU= github.com/goark/go-cvss v1.6.6/go.mod h1:H3qbfUSUlV7XtA3EwWNunvXz6OySwWHOuO+R6ZPMQPI= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= @@ -63,6 +67,9 @@ github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlW github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= +github.com/owenrumney/go-sarif/v2 v2.2.0 h1:1DmZaijK0HBZCR1fgcDSGa7VzYkU9NDmbZ7qC2QfUjE= +github.com/owenrumney/go-sarif/v2 v2.2.0/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU= github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= @@ -94,6 +101,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -101,11 +109,14 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -118,7 +129,9 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -154,7 +167,9 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= @@ -169,7 +184,9 @@ golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8 golang.org/x/vuln v1.0.0 h1:tYLAU3jD9LQr98Y+3el06lWyGMCnvzw06PIWP3LIy7g= golang.org/x/vuln v1.0.0/go.mod h1:V0eyhHwaAaHrt42J9bgrN6rd12f6GU4T0Lu0ex2wDg4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/internal/ci/fixtures/vulns/test-vuln-diff-a-b.json b/internal/ci/fixtures/vulns/test-vuln-diff-a-b.json new file mode 100644 index 0000000000..63ded601d0 --- /dev/null +++ b/internal/ci/fixtures/vulns/test-vuln-diff-a-b.json @@ -0,0 +1,122 @@ +{ + "results": [ + { + "source": { + "path": "/home/rexpan/Documents/Projects/scorecard-check-osv-e2e/go.mod", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "github.com/gogo/protobuf", + "version": "1.3.1", + "ecosystem": "Go" + }, + "vulnerabilities": [ + { + "modified": "2022-03-28T20:28:00Z", + "published": "2022-03-28T20:28:00Z", + "schema_version": "1.4.0", + "id": "GHSA-c3h9-896r-86jm", + "aliases": [ + "CVE-2021-3121" + ], + "summary": "Improper Input Validation in GoGo Protobuf", + "details": "An issue was discovered in GoGo Protobuf before 1.3.2. plugin/unmarshal/unmarshal.go lacks certain index validation, aka the \"skippy peanut butter\" issue.", + "affected": [ + { + "package": { + "ecosystem": "Go", + "name": "github.com/gogo/protobuf", + "purl": "pkg:golang/github.com/gogo/protobuf" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.3.2" + } + ] + } + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-c3h9-896r-86jm/GHSA-c3h9-896r-86jm.json" + } + } + ], + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H" + } + ], + "references": [ + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-3121" + }, + { + "type": "WEB", + "url": "https://github.com/gogo/protobuf/commit/b03c65ea87cdc3521ede29f62fe3ce239267c1bc" + }, + { + "type": "WEB", + "url": "https://discuss.hashicorp.com/t/hcsec-2021-23-consul-exposed-to-denial-of-service-in-gogo-protobuf-dependency/29025" + }, + { + "type": "PACKAGE", + "url": "https://github.com/gogo/protobuf" + }, + { + "type": "WEB", + "url": "https://github.com/gogo/protobuf/compare/v1.3.1...v1.3.2" + }, + { + "type": "WEB", + "url": "https://lists.apache.org/thread.html/r68032132c0399c29d6cdc7bd44918535da54060a10a12b1591328bff@%3Cnotifications.skywalking.apache.org%3E" + }, + { + "type": "WEB", + "url": "https://lists.apache.org/thread.html/r88d69555cb74a129a7bf84838073b61259b4a3830190e05a3b87994e@%3Ccommits.pulsar.apache.org%3E" + }, + { + "type": "WEB", + "url": "https://lists.apache.org/thread.html/rc1e9ff22c5641d73701ba56362fb867d40ed287cca000b131dcf4a44@%3Ccommits.pulsar.apache.org%3E" + }, + { + "type": "WEB", + "url": "https://pkg.go.dev/vuln/GO-2021-0053" + }, + { + "type": "WEB", + "url": "https://security.netapp.com/advisory/ntap-20210219-0006/" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-129", + "CWE-20" + ], + "github_reviewed": true, + "github_reviewed_at": "2022-03-28T20:28:00Z", + "nvd_published_at": "2021-01-11T06:15:00Z", + "severity": "HIGH" + } + } + ], + "groups": [ + { + "ids": [ + "GHSA-c3h9-896r-86jm" + ] + } + ] + } + ] + } + ] +} diff --git a/internal/ci/fixtures/vulns/test-vuln-diff-b-a.json b/internal/ci/fixtures/vulns/test-vuln-diff-b-a.json new file mode 100644 index 0000000000..8932c42630 --- /dev/null +++ b/internal/ci/fixtures/vulns/test-vuln-diff-b-a.json @@ -0,0 +1,3 @@ +{ + "results": [] +} diff --git a/internal/ci/fixtures/vulns/test-vuln-diff-b-c.json b/internal/ci/fixtures/vulns/test-vuln-diff-b-c.json new file mode 100644 index 0000000000..8932c42630 --- /dev/null +++ b/internal/ci/fixtures/vulns/test-vuln-diff-b-c.json @@ -0,0 +1,3 @@ +{ + "results": [] +} diff --git a/internal/ci/fixtures/vulns/test-vuln-diff-c-b.json b/internal/ci/fixtures/vulns/test-vuln-diff-c-b.json new file mode 100644 index 0000000000..fa4d36049c --- /dev/null +++ b/internal/ci/fixtures/vulns/test-vuln-diff-c-b.json @@ -0,0 +1,329 @@ +{ + "results": [ + { + "source": { + "path": "/home/rexpan/Documents/Projects/scorecard-check-osv-e2e/go.mod", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "github.com/gogo/protobuf", + "version": "1.3.1", + "ecosystem": "Go" + }, + "vulnerabilities": [ + { + "modified": "2022-03-28T20:28:00Z", + "published": "2022-03-28T20:28:00Z", + "schema_version": "1.4.0", + "id": "GHSA-c3h9-896r-86jm", + "aliases": [ + "CVE-2021-3121" + ], + "summary": "Improper Input Validation in GoGo Protobuf", + "details": "An issue was discovered in GoGo Protobuf before 1.3.2. plugin/unmarshal/unmarshal.go lacks certain index validation, aka the \"skippy peanut butter\" issue.", + "affected": [ + { + "package": { + "ecosystem": "Go", + "name": "github.com/gogo/protobuf", + "purl": "pkg:golang/github.com/gogo/protobuf" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.3.2" + } + ] + } + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-c3h9-896r-86jm/GHSA-c3h9-896r-86jm.json" + } + } + ], + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H" + } + ], + "references": [ + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-3121" + }, + { + "type": "WEB", + "url": "https://github.com/gogo/protobuf/commit/b03c65ea87cdc3521ede29f62fe3ce239267c1bc" + }, + { + "type": "WEB", + "url": "https://discuss.hashicorp.com/t/hcsec-2021-23-consul-exposed-to-denial-of-service-in-gogo-protobuf-dependency/29025" + }, + { + "type": "PACKAGE", + "url": "https://github.com/gogo/protobuf" + }, + { + "type": "WEB", + "url": "https://github.com/gogo/protobuf/compare/v1.3.1...v1.3.2" + }, + { + "type": "WEB", + "url": "https://lists.apache.org/thread.html/r68032132c0399c29d6cdc7bd44918535da54060a10a12b1591328bff@%3Cnotifications.skywalking.apache.org%3E" + }, + { + "type": "WEB", + "url": "https://lists.apache.org/thread.html/r88d69555cb74a129a7bf84838073b61259b4a3830190e05a3b87994e@%3Ccommits.pulsar.apache.org%3E" + }, + { + "type": "WEB", + "url": "https://lists.apache.org/thread.html/rc1e9ff22c5641d73701ba56362fb867d40ed287cca000b131dcf4a44@%3Ccommits.pulsar.apache.org%3E" + }, + { + "type": "WEB", + "url": "https://pkg.go.dev/vuln/GO-2021-0053" + }, + { + "type": "WEB", + "url": "https://security.netapp.com/advisory/ntap-20210219-0006/" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-129", + "CWE-20" + ], + "github_reviewed": true, + "github_reviewed_at": "2022-03-28T20:28:00Z", + "nvd_published_at": "2021-01-11T06:15:00Z", + "severity": "HIGH" + } + } + ], + "groups": [ + { + "ids": [ + "GHSA-c3h9-896r-86jm" + ] + } + ] + } + ] + }, + { + "source": { + "path": "/home/rexpan/Documents/Projects/scorecard-check-osv-e2e/sub-rust-project/Cargo.lock", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "regex", + "version": "1.5.1", + "ecosystem": "crates.io" + }, + "vulnerabilities": [ + { + "modified": "2022-08-11T20:38:52Z", + "published": "2022-03-08T20:00:36Z", + "schema_version": "1.4.0", + "id": "GHSA-m5pq-gvj9-9vr8", + "aliases": [ + "CVE-2022-24713" + ], + "summary": "Rust's regex crate vulnerable to regular expression denial of service", + "details": "\u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\n[advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\nThe Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.", + "affected": [ + { + "package": { + "ecosystem": "crates.io", + "name": "regex", + "purl": "pkg:cargo/regex" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.5.5" + } + ] + } + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-m5pq-gvj9-9vr8/GHSA-m5pq-gvj9-9vr8.json" + } + } + ], + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + } + ], + "references": [ + { + "type": "WEB", + "url": "https://github.com/rust-lang/regex/security/advisories/GHSA-m5pq-gvj9-9vr8" + }, + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24713" + }, + { + "type": "WEB", + "url": "https://github.com/rust-lang/regex/commit/ae70b41d4f46641dbc45c7a4f87954aea356283e" + }, + { + "type": "PACKAGE", + "url": "https://github.com/rust-lang/regex/" + }, + { + "type": "WEB", + "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw" + }, + { + "type": "WEB", + "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00003.html" + }, + { + "type": "WEB", + "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00009.html" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JANLZ3JXWJR7FSHE57K66UIZUIJZI67T/" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/O3YB7CURSG64CIPCDPNMGPE4UU24AB6H/" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/PDOWTHNVGBOP2HN27PUFIGRYNSNDTYRJ/" + }, + { + "type": "WEB", + "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html" + }, + { + "type": "WEB", + "url": "https://security.gentoo.org/glsa/202208-08" + }, + { + "type": "WEB", + "url": "https://security.gentoo.org/glsa/202208-14" + }, + { + "type": "WEB", + "url": "https://www.debian.org/security/2022/dsa-5113" + }, + { + "type": "WEB", + "url": "https://www.debian.org/security/2022/dsa-5118" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-400" + ], + "github_reviewed": true, + "github_reviewed_at": "2022-03-08T20:00:36Z", + "nvd_published_at": "2022-03-08T19:15:00Z", + "severity": "HIGH" + } + }, + { + "modified": "2023-06-13T13:10:24Z", + "published": "2022-03-08T12:00:00Z", + "schema_version": "1.4.0", + "id": "RUSTSEC-2022-0013", + "aliases": [ + "CVE-2022-24713", + "GHSA-m5pq-gvj9-9vr8" + ], + "summary": "Regexes with large repetitions on empty sub-expressions take a very long time to parse", + "details": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security", + "affected": [ + { + "package": { + "ecosystem": "crates.io", + "name": "regex", + "purl": "pkg:cargo/regex" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0.0.0-0" + }, + { + "fixed": "1.5.5" + } + ] + } + ], + "database_specific": { + "categories": [ + "denial-of-service" + ], + "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "informational": null, + "source": "https://github.com/rustsec/advisory-db/blob/osv/crates/RUSTSEC-2022-0013.json" + }, + "ecosystem_specific": { + "affects": { + "arch": [], + "functions": [], + "os": [] + } + } + } + ], + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + } + ], + "references": [ + { + "type": "PACKAGE", + "url": "https://crates.io/crates/regex" + }, + { + "type": "ADVISORY", + "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html" + }, + { + "type": "WEB", + "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw" + } + ] + } + ], + "groups": [ + { + "ids": [ + "GHSA-m5pq-gvj9-9vr8", + "RUSTSEC-2022-0013" + ] + } + ] + } + ] + } + ] +} diff --git a/internal/ci/fixtures/vulns/test-vuln-results-a.json b/internal/ci/fixtures/vulns/test-vuln-results-a.json new file mode 100644 index 0000000000..16662c9575 --- /dev/null +++ b/internal/ci/fixtures/vulns/test-vuln-results-a.json @@ -0,0 +1,292 @@ +{ + "results": [ + { + "source": { + "path": "/home/rexpan/Documents/Projects/scorecard-check-osv-e2e/go.mod", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "github.com/gogo/protobuf", + "version": "1.3.1", + "ecosystem": "Go" + }, + "vulnerabilities": [ + { + "modified": "2023-06-12T18:45:41Z", + "published": "2021-04-14T20:04:52Z", + "schema_version": "1.4.0", + "id": "GO-2021-0053", + "aliases": [ + "CVE-2021-3121", + "GHSA-c3h9-896r-86jm" + ], + "summary": "Panic due to improper input validation in github.com/gogo/protobuf", + "details": "Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.", + "affected": [ + { + "package": { + "ecosystem": "Go", + "name": "github.com/gogo/protobuf", + "purl": "pkg:golang/github.com/gogo/protobuf" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.3.2" + } + ] + } + ], + "database_specific": { + "source": "https://vuln.go.dev/ID/GO-2021-0053.json" + }, + "ecosystem_specific": { + "imports": [ + { + "path": "github.com/gogo/protobuf/plugin/unmarshal", + "symbols": [ + "unmarshal.Generate", + "unmarshal.field" + ] + } + ] + } + } + ], + "references": [ + { + "type": "FIX", + "url": "https://github.com/gogo/protobuf/commit/b03c65ea87cdc3521ede29f62fe3ce239267c1bc" + } + ], + "database_specific": { + "url": "https://pkg.go.dev/vuln/GO-2021-0053" + } + } + ], + "groups": [ + { + "ids": [ + "GHSA-c3h9-896r-86jm" + ] + } + ] + } + ] + }, + { + "source": { + "path": "/home/rexpan/Documents/Projects/scorecard-check-osv-e2e/sub-rust-project/Cargo.lock", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "regex", + "version": "1.5.1", + "ecosystem": "crates.io" + }, + "vulnerabilities": [ + { + "modified": "2022-08-11T20:38:52Z", + "published": "2022-03-08T20:00:36Z", + "schema_version": "1.4.0", + "id": "GHSA-m5pq-gvj9-9vr8", + "aliases": [ + "CVE-2022-24713" + ], + "summary": "Rust's regex crate vulnerable to regular expression denial of service", + "details": "\u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\n[advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\nThe Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.", + "affected": [ + { + "package": { + "ecosystem": "crates.io", + "name": "regex", + "purl": "pkg:cargo/regex" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.5.5" + } + ] + } + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-m5pq-gvj9-9vr8/GHSA-m5pq-gvj9-9vr8.json" + } + } + ], + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + } + ], + "references": [ + { + "type": "WEB", + "url": "https://github.com/rust-lang/regex/security/advisories/GHSA-m5pq-gvj9-9vr8" + }, + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24713" + }, + { + "type": "WEB", + "url": "https://github.com/rust-lang/regex/commit/ae70b41d4f46641dbc45c7a4f87954aea356283e" + }, + { + "type": "PACKAGE", + "url": "https://github.com/rust-lang/regex/" + }, + { + "type": "WEB", + "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw" + }, + { + "type": "WEB", + "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00003.html" + }, + { + "type": "WEB", + "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00009.html" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JANLZ3JXWJR7FSHE57K66UIZUIJZI67T/" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/O3YB7CURSG64CIPCDPNMGPE4UU24AB6H/" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/PDOWTHNVGBOP2HN27PUFIGRYNSNDTYRJ/" + }, + { + "type": "WEB", + "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html" + }, + { + "type": "WEB", + "url": "https://security.gentoo.org/glsa/202208-08" + }, + { + "type": "WEB", + "url": "https://security.gentoo.org/glsa/202208-14" + }, + { + "type": "WEB", + "url": "https://www.debian.org/security/2022/dsa-5113" + }, + { + "type": "WEB", + "url": "https://www.debian.org/security/2022/dsa-5118" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-400" + ], + "github_reviewed": true, + "github_reviewed_at": "2022-03-08T20:00:36Z", + "nvd_published_at": "2022-03-08T19:15:00Z", + "severity": "HIGH" + } + }, + { + "modified": "2023-06-13T13:10:24Z", + "published": "2022-03-08T12:00:00Z", + "schema_version": "1.4.0", + "id": "RUSTSEC-2022-0013", + "aliases": [ + "CVE-2022-24713", + "GHSA-m5pq-gvj9-9vr8" + ], + "summary": "Regexes with large repetitions on empty sub-expressions take a very long time to parse", + "details": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security", + "affected": [ + { + "package": { + "ecosystem": "crates.io", + "name": "regex", + "purl": "pkg:cargo/regex" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0.0.0-0" + }, + { + "fixed": "1.5.5" + } + ] + } + ], + "database_specific": { + "categories": [ + "denial-of-service" + ], + "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "informational": null, + "source": "https://github.com/rustsec/advisory-db/blob/osv/crates/RUSTSEC-2022-0013.json" + }, + "ecosystem_specific": { + "affects": { + "arch": [], + "functions": [], + "os": [] + } + } + } + ], + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + } + ], + "references": [ + { + "type": "PACKAGE", + "url": "https://crates.io/crates/regex" + }, + { + "type": "ADVISORY", + "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html" + }, + { + "type": "WEB", + "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw" + } + ] + } + ], + "groups": [ + { + "ids": [ + "GHSA-m5pq-gvj9-9vr8", + "RUSTSEC-2022-0013" + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/internal/ci/fixtures/vulns/test-vuln-results-b.json b/internal/ci/fixtures/vulns/test-vuln-results-b.json new file mode 100644 index 0000000000..dd8bf7d148 --- /dev/null +++ b/internal/ci/fixtures/vulns/test-vuln-results-b.json @@ -0,0 +1,387 @@ +{ + "results": [ + { + "source": { + "path": "/home/rexpan/Documents/Projects/scorecard-check-osv-e2e/go.mod", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "github.com/gogo/protobuf", + "version": "1.3.1", + "ecosystem": "Go" + }, + "vulnerabilities": [ + { + "modified": "2022-03-28T20:28:00Z", + "published": "2022-03-28T20:28:00Z", + "schema_version": "1.4.0", + "id": "GHSA-c3h9-896r-86jm", + "aliases": [ + "CVE-2021-3121" + ], + "summary": "Improper Input Validation in GoGo Protobuf", + "details": "An issue was discovered in GoGo Protobuf before 1.3.2. plugin/unmarshal/unmarshal.go lacks certain index validation, aka the \"skippy peanut butter\" issue.", + "affected": [ + { + "package": { + "ecosystem": "Go", + "name": "github.com/gogo/protobuf", + "purl": "pkg:golang/github.com/gogo/protobuf" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.3.2" + } + ] + } + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-c3h9-896r-86jm/GHSA-c3h9-896r-86jm.json" + } + } + ], + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H" + } + ], + "references": [ + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-3121" + }, + { + "type": "WEB", + "url": "https://github.com/gogo/protobuf/commit/b03c65ea87cdc3521ede29f62fe3ce239267c1bc" + }, + { + "type": "WEB", + "url": "https://discuss.hashicorp.com/t/hcsec-2021-23-consul-exposed-to-denial-of-service-in-gogo-protobuf-dependency/29025" + }, + { + "type": "PACKAGE", + "url": "https://github.com/gogo/protobuf" + }, + { + "type": "WEB", + "url": "https://github.com/gogo/protobuf/compare/v1.3.1...v1.3.2" + }, + { + "type": "WEB", + "url": "https://lists.apache.org/thread.html/r68032132c0399c29d6cdc7bd44918535da54060a10a12b1591328bff@%3Cnotifications.skywalking.apache.org%3E" + }, + { + "type": "WEB", + "url": "https://lists.apache.org/thread.html/r88d69555cb74a129a7bf84838073b61259b4a3830190e05a3b87994e@%3Ccommits.pulsar.apache.org%3E" + }, + { + "type": "WEB", + "url": "https://lists.apache.org/thread.html/rc1e9ff22c5641d73701ba56362fb867d40ed287cca000b131dcf4a44@%3Ccommits.pulsar.apache.org%3E" + }, + { + "type": "WEB", + "url": "https://pkg.go.dev/vuln/GO-2021-0053" + }, + { + "type": "WEB", + "url": "https://security.netapp.com/advisory/ntap-20210219-0006/" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-129", + "CWE-20" + ], + "github_reviewed": true, + "github_reviewed_at": "2022-03-28T20:28:00Z", + "nvd_published_at": "2021-01-11T06:15:00Z", + "severity": "HIGH" + } + }, + { + "modified": "2023-06-12T18:45:41Z", + "published": "2021-04-14T20:04:52Z", + "schema_version": "1.4.0", + "id": "GO-2021-0053", + "aliases": [ + "CVE-2021-3121", + "GHSA-c3h9-896r-86jm" + ], + "summary": "Panic due to improper input validation in github.com/gogo/protobuf", + "details": "Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.", + "affected": [ + { + "package": { + "ecosystem": "Go", + "name": "github.com/gogo/protobuf", + "purl": "pkg:golang/github.com/gogo/protobuf" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.3.2" + } + ] + } + ], + "database_specific": { + "source": "https://vuln.go.dev/ID/GO-2021-0053.json" + }, + "ecosystem_specific": { + "imports": [ + { + "path": "github.com/gogo/protobuf/plugin/unmarshal", + "symbols": [ + "unmarshal.Generate", + "unmarshal.field" + ] + } + ] + } + } + ], + "references": [ + { + "type": "FIX", + "url": "https://github.com/gogo/protobuf/commit/b03c65ea87cdc3521ede29f62fe3ce239267c1bc" + } + ], + "database_specific": { + "url": "https://pkg.go.dev/vuln/GO-2021-0053" + } + } + ], + "groups": [ + { + "ids": [ + "GHSA-c3h9-896r-86jm", + "GO-2021-0053" + ] + } + ] + } + ] + }, + { + "source": { + "path": "/home/rexpan/Documents/Projects/scorecard-check-osv-e2e/sub-rust-project/Cargo.lock", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "regex", + "version": "1.5.1", + "ecosystem": "crates.io" + }, + "vulnerabilities": [ + { + "modified": "2022-08-11T20:38:52Z", + "published": "2022-03-08T20:00:36Z", + "schema_version": "1.4.0", + "id": "GHSA-m5pq-gvj9-9vr8", + "aliases": [ + "CVE-2022-24713" + ], + "summary": "Rust's regex crate vulnerable to regular expression denial of service", + "details": "\u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\n[advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\nThe Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.", + "affected": [ + { + "package": { + "ecosystem": "crates.io", + "name": "regex", + "purl": "pkg:cargo/regex" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.5.5" + } + ] + } + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-m5pq-gvj9-9vr8/GHSA-m5pq-gvj9-9vr8.json" + } + } + ], + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + } + ], + "references": [ + { + "type": "WEB", + "url": "https://github.com/rust-lang/regex/security/advisories/GHSA-m5pq-gvj9-9vr8" + }, + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24713" + }, + { + "type": "WEB", + "url": "https://github.com/rust-lang/regex/commit/ae70b41d4f46641dbc45c7a4f87954aea356283e" + }, + { + "type": "PACKAGE", + "url": "https://github.com/rust-lang/regex/" + }, + { + "type": "WEB", + "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw" + }, + { + "type": "WEB", + "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00003.html" + }, + { + "type": "WEB", + "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00009.html" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JANLZ3JXWJR7FSHE57K66UIZUIJZI67T/" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/O3YB7CURSG64CIPCDPNMGPE4UU24AB6H/" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/PDOWTHNVGBOP2HN27PUFIGRYNSNDTYRJ/" + }, + { + "type": "WEB", + "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html" + }, + { + "type": "WEB", + "url": "https://security.gentoo.org/glsa/202208-08" + }, + { + "type": "WEB", + "url": "https://security.gentoo.org/glsa/202208-14" + }, + { + "type": "WEB", + "url": "https://www.debian.org/security/2022/dsa-5113" + }, + { + "type": "WEB", + "url": "https://www.debian.org/security/2022/dsa-5118" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-400" + ], + "github_reviewed": true, + "github_reviewed_at": "2022-03-08T20:00:36Z", + "nvd_published_at": "2022-03-08T19:15:00Z", + "severity": "HIGH" + } + }, + { + "modified": "2023-06-13T13:10:24Z", + "published": "2022-03-08T12:00:00Z", + "schema_version": "1.4.0", + "id": "RUSTSEC-2022-0013", + "aliases": [ + "CVE-2022-24713", + "GHSA-m5pq-gvj9-9vr8" + ], + "summary": "Regexes with large repetitions on empty sub-expressions take a very long time to parse", + "details": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security", + "affected": [ + { + "package": { + "ecosystem": "crates.io", + "name": "regex", + "purl": "pkg:cargo/regex" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0.0.0-0" + }, + { + "fixed": "1.5.5" + } + ] + } + ], + "database_specific": { + "categories": [ + "denial-of-service" + ], + "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "informational": null, + "source": "https://github.com/rustsec/advisory-db/blob/osv/crates/RUSTSEC-2022-0013.json" + }, + "ecosystem_specific": { + "affects": { + "arch": [], + "functions": [], + "os": [] + } + } + } + ], + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + } + ], + "references": [ + { + "type": "PACKAGE", + "url": "https://crates.io/crates/regex" + }, + { + "type": "ADVISORY", + "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html" + }, + { + "type": "WEB", + "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw" + } + ] + } + ], + "groups": [ + { + "ids": [ + "GHSA-m5pq-gvj9-9vr8", + "RUSTSEC-2022-0013" + ] + } + ] + } + ] + } + ] +} diff --git a/internal/ci/fixtures/vulns/test-vuln-results-c.json b/internal/ci/fixtures/vulns/test-vuln-results-c.json new file mode 100644 index 0000000000..c3fd584d5b --- /dev/null +++ b/internal/ci/fixtures/vulns/test-vuln-results-c.json @@ -0,0 +1,85 @@ +{ + "results": [ + { + "source": { + "path": "/home/rexpan/Documents/Projects/scorecard-check-osv-e2e/go.mod", + "type": "lockfile" + }, + "packages": [ + { + "package": { + "name": "github.com/gogo/protobuf", + "version": "1.3.1", + "ecosystem": "Go" + }, + "vulnerabilities": [ + { + "modified": "2023-06-12T18:45:41Z", + "published": "2021-04-14T20:04:52Z", + "schema_version": "1.4.0", + "id": "GO-2021-0053", + "aliases": [ + "CVE-2021-3121", + "GHSA-c3h9-896r-86jm" + ], + "summary": "Panic due to improper input validation in github.com/gogo/protobuf", + "details": "Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.", + "affected": [ + { + "package": { + "ecosystem": "Go", + "name": "github.com/gogo/protobuf", + "purl": "pkg:golang/github.com/gogo/protobuf" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.3.2" + } + ] + } + ], + "database_specific": { + "source": "https://vuln.go.dev/ID/GO-2021-0053.json" + }, + "ecosystem_specific": { + "imports": [ + { + "path": "github.com/gogo/protobuf/plugin/unmarshal", + "symbols": [ + "unmarshal.Generate", + "unmarshal.field" + ] + } + ] + } + } + ], + "references": [ + { + "type": "FIX", + "url": "https://github.com/gogo/protobuf/commit/b03c65ea87cdc3521ede29f62fe3ce239267c1bc" + } + ], + "database_specific": { + "url": "https://pkg.go.dev/vuln/GO-2021-0053" + } + } + ], + "groups": [ + { + "ids": [ + "GHSA-c3h9-896r-86jm" + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/internal/ci/utility.go b/internal/ci/utility.go new file mode 100644 index 0000000000..24ee2bb613 --- /dev/null +++ b/internal/ci/utility.go @@ -0,0 +1,23 @@ +package ci + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/google/osv-scanner/pkg/models" +) + +func LoadVulnResults(path string) (models.VulnerabilityResults, error) { + file, err := os.Open(path) + if err != nil { + return models.VulnerabilityResults{}, fmt.Errorf("failed to load '%s'", path) + } + var value models.VulnerabilityResults + err = json.NewDecoder(file).Decode(&value) + if err != nil { + return models.VulnerabilityResults{}, fmt.Errorf("failed to parse '%s'", path) + } + + return value, nil +} diff --git a/internal/ci/vulnerability_result_diff.go b/internal/ci/vulnerability_result_diff.go new file mode 100644 index 0000000000..fa2a75f5dc --- /dev/null +++ b/internal/ci/vulnerability_result_diff.go @@ -0,0 +1,67 @@ +package ci + +import ( + "github.com/google/osv-scanner/pkg/grouper" + "github.com/google/osv-scanner/pkg/models" + "golang.org/x/exp/slices" +) + +// DiffVulnerabilityResults will return any new vulnerabilities that are in `newRes` +// which is not present in `oldRes`, but not the reverse. +// +// Current implementation is O(n^2) on the number of vulns, but can be reduced to linear time +func DiffVulnerabilityResults(oldRes, newRes models.VulnerabilityResults) models.VulnerabilityResults { + result := models.VulnerabilityResults{} + for _, ps := range newRes.Results { + sourceIdx := slices.IndexFunc(oldRes.Results, func(elem models.PackageSource) bool { return elem.Source == ps.Source }) + if sourceIdx == -1 { + // Newly introduced source, so all results for this source are going to be new, add everything for this source + result.Results = append(result.Results, ps) + continue + } + // Otherwise the old source used to exist, so we need to find the difference in the packages + result.Results = append(result.Results, models.PackageSource{ + Source: ps.Source, + }) + resultPS := &result.Results[len(result.Results)-1] + for _, pv := range ps.Packages { + pkgs := oldRes.Results[sourceIdx].Packages + pkgIdx := slices.IndexFunc(pkgs, func(elem models.PackageVulns) bool { return elem.Package == pv.Package }) + if pkgIdx == -1 { + // Newly introduced package, so all results for this package are going to be new, add everything for this package + resultPS.Packages = append(resultPS.Packages, pv) + continue + } + // Otherwise the old package used to exist, so we need to find the difference in the vulnerabilities + // Only copy over packages as vulns and groups might change + resultPS.Packages = append(resultPS.Packages, models.PackageVulns{ + Package: pv.Package, + }) + resultPV := &resultPS.Packages[len(resultPS.Packages)-1] + for _, v := range pv.Vulnerabilities { + vulns := pkgs[pkgIdx].Vulnerabilities + vulnIdx := slices.IndexFunc(vulns, func(elem models.Vulnerability) bool { return elem.ID == v.ID }) + if vulnIdx == -1 { + // Vulnerability is new, add it to the results + resultPV.Vulnerabilities = append(resultPV.Vulnerabilities, v) + continue + } + } + if len(resultPV.Vulnerabilities) == 0 { + // No vulns, so we can remove the PackageVulns entry entirely, and skip grouping + resultPS.Packages = resultPS.Packages[:len(resultPS.Packages)-1] + continue + } + // Rebuild the groups lost in the previous step + groups := grouper.Group(grouper.ConvertVulnerabilityToIDAliases(resultPV.Vulnerabilities)) + resultPV.Groups = groups + } + if len(resultPS.Packages) == 0 { + // No packages, so we can remove the PackageSource entry entirely + result.Results = result.Results[:len(result.Results)-1] + continue + } + } + + return result +} diff --git a/internal/ci/vulnerability_result_diff_test.go b/internal/ci/vulnerability_result_diff_test.go new file mode 100644 index 0000000000..fd077c7942 --- /dev/null +++ b/internal/ci/vulnerability_result_diff_test.go @@ -0,0 +1,63 @@ +package ci_test + +import ( + "testing" + + "github.com/google/osv-scanner/internal/ci" + "github.com/google/osv-scanner/internal/testutility" + "github.com/google/osv-scanner/pkg/models" +) + +func TestDiffVulnerabilityResults(t *testing.T) { + t.Parallel() + type args struct { + oldRes models.VulnerabilityResults + newRes models.VulnerabilityResults + } + tests := []struct { + name string + args args + wantPath string + }{ + { + args: args{ + oldRes: testutility.LoadJSONFixture[models.VulnerabilityResults](t, "fixtures/vulns/test-vuln-results-a.json"), + newRes: testutility.LoadJSONFixture[models.VulnerabilityResults](t, "fixtures/vulns/test-vuln-results-b.json"), + }, + // `newRes` has one new GO vuln compared to `oldRes`, so the result should contain just the extra vuln + wantPath: "fixtures/vulns/test-vuln-diff-a-b.json", + }, + { + args: args{ + oldRes: testutility.LoadJSONFixture[models.VulnerabilityResults](t, "fixtures/vulns/test-vuln-results-b.json"), + newRes: testutility.LoadJSONFixture[models.VulnerabilityResults](t, "fixtures/vulns/test-vuln-results-a.json"), + }, + // `oldRes` has one new GO vuln compared to `newRes`, so the result should be empty + wantPath: "fixtures/vulns/test-vuln-diff-b-a.json", + }, + { + args: args{ + oldRes: testutility.LoadJSONFixture[models.VulnerabilityResults](t, "fixtures/vulns/test-vuln-results-c.json"), + newRes: testutility.LoadJSONFixture[models.VulnerabilityResults](t, "fixtures/vulns/test-vuln-results-b.json"), + }, + // `newRes` has one new GO vuln, and a new `cargo.toml` source compared to `oldRes`, so the result should contain all the new vulns + wantPath: "fixtures/vulns/test-vuln-diff-c-b.json", + }, + { + args: args{ + oldRes: testutility.LoadJSONFixture[models.VulnerabilityResults](t, "fixtures/vulns/test-vuln-results-b.json"), + newRes: testutility.LoadJSONFixture[models.VulnerabilityResults](t, "fixtures/vulns/test-vuln-results-c.json"), + }, + // opposite of above test case, result should be empty + wantPath: "fixtures/vulns/test-vuln-diff-b-c.json", + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := ci.DiffVulnerabilityResults(tt.args.oldRes, tt.args.newRes) + testutility.AssertMatchFixtureJSON(t, tt.wantPath, got) + }) + } +} diff --git a/internal/output/fixtures/flattened_vulns.json b/internal/output/fixtures/flattened_vulns.json new file mode 100644 index 0000000000..96f023d6d7 --- /dev/null +++ b/internal/output/fixtures/flattened_vulns.json @@ -0,0 +1,403 @@ +[ + { + "Source": { + "path": "/path/to/scorecard-check-osv-e2e/go.mod", + "type": "lockfile" + }, + "Package": { + "name": "github.com/gogo/protobuf", + "version": "1.3.1", + "ecosystem": "Go" + }, + "Vulnerability": { + "modified": "2022-03-28T20:28:00Z", + "published": "2022-03-28T20:28:00Z", + "schema_version": "1.4.0", + "id": "GHSA-c3h9-896r-86jm", + "aliases": [ + "CVE-2021-3121" + ], + "summary": "Improper Input Validation in GoGo Protobuf", + "details": "An issue was discovered in GoGo Protobuf before 1.3.2. plugin/unmarshal/unmarshal.go lacks certain index validation, aka the \"skippy peanut butter\" issue.", + "affected": [ + { + "package": { + "ecosystem": "Go", + "name": "github.com/gogo/protobuf", + "purl": "pkg:golang/github.com/gogo/protobuf" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.3.2" + } + ] + } + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-c3h9-896r-86jm/GHSA-c3h9-896r-86jm.json" + } + } + ], + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H" + } + ], + "references": [ + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-3121" + }, + { + "type": "WEB", + "url": "https://github.com/gogo/protobuf/commit/b03c65ea87cdc3521ede29f62fe3ce239267c1bc" + }, + { + "type": "WEB", + "url": "https://discuss.hashicorp.com/t/hcsec-2021-23-consul-exposed-to-denial-of-service-in-gogo-protobuf-dependency/29025" + }, + { + "type": "PACKAGE", + "url": "https://github.com/gogo/protobuf" + }, + { + "type": "WEB", + "url": "https://github.com/gogo/protobuf/compare/v1.3.1...v1.3.2" + }, + { + "type": "WEB", + "url": "https://lists.apache.org/thread.html/r68032132c0399c29d6cdc7bd44918535da54060a10a12b1591328bff@%3Cnotifications.skywalking.apache.org%3E" + }, + { + "type": "WEB", + "url": "https://lists.apache.org/thread.html/r88d69555cb74a129a7bf84838073b61259b4a3830190e05a3b87994e@%3Ccommits.pulsar.apache.org%3E" + }, + { + "type": "WEB", + "url": "https://lists.apache.org/thread.html/rc1e9ff22c5641d73701ba56362fb867d40ed287cca000b131dcf4a44@%3Ccommits.pulsar.apache.org%3E" + }, + { + "type": "WEB", + "url": "https://pkg.go.dev/vuln/GO-2021-0053" + }, + { + "type": "WEB", + "url": "https://security.netapp.com/advisory/ntap-20210219-0006/" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-129", + "CWE-20" + ], + "github_reviewed": true, + "github_reviewed_at": "2022-03-28T20:28:00Z", + "nvd_published_at": "2021-01-11T06:15:00Z", + "severity": "HIGH" + } + }, + "GroupInfo": { + "ids": [ + "GHSA-c3h9-896r-86jm", + "GO-2021-0053" + ] + } + }, + { + "Source": { + "path": "/path/to/scorecard-check-osv-e2e/go.mod", + "type": "lockfile" + }, + "Package": { + "name": "github.com/gogo/protobuf", + "version": "1.3.1", + "ecosystem": "Go" + }, + "Vulnerability": { + "modified": "2023-06-12T18:45:41Z", + "published": "2021-04-14T20:04:52Z", + "schema_version": "1.4.0", + "id": "GO-2021-0053", + "aliases": [ + "CVE-2021-3121", + "GHSA-c3h9-896r-86jm" + ], + "summary": "Panic due to improper input validation in github.com/gogo/protobuf", + "details": "Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.", + "affected": [ + { + "package": { + "ecosystem": "Go", + "name": "github.com/gogo/protobuf", + "purl": "pkg:golang/github.com/gogo/protobuf" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.3.2" + } + ] + } + ], + "database_specific": { + "source": "https://vuln.go.dev/ID/GO-2021-0053.json" + }, + "ecosystem_specific": { + "imports": [ + { + "path": "github.com/gogo/protobuf/plugin/unmarshal", + "symbols": [ + "unmarshal.Generate", + "unmarshal.field" + ] + } + ] + } + } + ], + "references": [ + { + "type": "FIX", + "url": "https://github.com/gogo/protobuf/commit/b03c65ea87cdc3521ede29f62fe3ce239267c1bc" + } + ], + "database_specific": { + "url": "https://pkg.go.dev/vuln/GO-2021-0053" + } + }, + "GroupInfo": { + "ids": [ + "GHSA-c3h9-896r-86jm", + "GO-2021-0053" + ] + } + }, + { + "Source": { + "path": "/path/to/scorecard-check-osv-e2e/sub-rust-project/Cargo.lock", + "type": "lockfile" + }, + "Package": { + "name": "regex", + "version": "1.5.1", + "ecosystem": "crates.io" + }, + "Vulnerability": { + "modified": "2022-08-11T20:38:52Z", + "published": "2022-03-08T20:00:36Z", + "schema_version": "1.4.0", + "id": "GHSA-m5pq-gvj9-9vr8", + "aliases": [ + "CVE-2022-24713" + ], + "summary": "Rust's regex crate vulnerable to regular expression denial of service", + "details": "\u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\n[advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\nThe Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.", + "affected": [ + { + "package": { + "ecosystem": "crates.io", + "name": "regex", + "purl": "pkg:cargo/regex" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.5.5" + } + ] + } + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-m5pq-gvj9-9vr8/GHSA-m5pq-gvj9-9vr8.json" + } + } + ], + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + } + ], + "references": [ + { + "type": "WEB", + "url": "https://github.com/rust-lang/regex/security/advisories/GHSA-m5pq-gvj9-9vr8" + }, + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24713" + }, + { + "type": "WEB", + "url": "https://github.com/rust-lang/regex/commit/ae70b41d4f46641dbc45c7a4f87954aea356283e" + }, + { + "type": "PACKAGE", + "url": "https://github.com/rust-lang/regex/" + }, + { + "type": "WEB", + "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw" + }, + { + "type": "WEB", + "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00003.html" + }, + { + "type": "WEB", + "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00009.html" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JANLZ3JXWJR7FSHE57K66UIZUIJZI67T/" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/O3YB7CURSG64CIPCDPNMGPE4UU24AB6H/" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/PDOWTHNVGBOP2HN27PUFIGRYNSNDTYRJ/" + }, + { + "type": "WEB", + "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html" + }, + { + "type": "WEB", + "url": "https://security.gentoo.org/glsa/202208-08" + }, + { + "type": "WEB", + "url": "https://security.gentoo.org/glsa/202208-14" + }, + { + "type": "WEB", + "url": "https://www.debian.org/security/2022/dsa-5113" + }, + { + "type": "WEB", + "url": "https://www.debian.org/security/2022/dsa-5118" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-400" + ], + "github_reviewed": true, + "github_reviewed_at": "2022-03-08T20:00:36Z", + "nvd_published_at": "2022-03-08T19:15:00Z", + "severity": "HIGH" + } + }, + "GroupInfo": { + "ids": [ + "GHSA-m5pq-gvj9-9vr8", + "RUSTSEC-2022-0013" + ] + } + }, + { + "Source": { + "path": "/path/to/scorecard-check-osv-e2e/sub-rust-project/Cargo.lock", + "type": "lockfile" + }, + "Package": { + "name": "regex", + "version": "1.5.1", + "ecosystem": "crates.io" + }, + "Vulnerability": { + "modified": "2023-06-13T13:10:24Z", + "published": "2022-03-08T12:00:00Z", + "schema_version": "1.4.0", + "id": "RUSTSEC-2022-0013", + "aliases": [ + "CVE-2022-24713", + "GHSA-m5pq-gvj9-9vr8" + ], + "summary": "Regexes with large repetitions on empty sub-expressions take a very long time to parse", + "details": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security", + "affected": [ + { + "package": { + "ecosystem": "crates.io", + "name": "regex", + "purl": "pkg:cargo/regex" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0.0.0-0" + }, + { + "fixed": "1.5.5" + } + ] + } + ], + "database_specific": { + "categories": [ + "denial-of-service" + ], + "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "informational": null, + "source": "https://github.com/rustsec/advisory-db/blob/osv/crates/RUSTSEC-2022-0013.json" + }, + "ecosystem_specific": { + "affects": { + "arch": [], + "functions": [], + "os": [] + } + } + } + ], + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + } + ], + "references": [ + { + "type": "PACKAGE", + "url": "https://crates.io/crates/regex" + }, + { + "type": "ADVISORY", + "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html" + }, + { + "type": "WEB", + "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw" + } + ] + }, + "GroupInfo": { + "ids": [ + "GHSA-m5pq-gvj9-9vr8", + "RUSTSEC-2022-0013" + ] + } + } +] \ No newline at end of file diff --git a/internal/output/fixtures/group_fixed_version_output.json b/internal/output/fixtures/group_fixed_version_output.json new file mode 100644 index 0000000000..ac488cb745 --- /dev/null +++ b/internal/output/fixtures/group_fixed_version_output.json @@ -0,0 +1,8 @@ +{ + "lockfile:/path/to/scorecard-check-osv-e2e/go.mod:GHSA-c3h9-896r-86jm,GO-2021-0053": [ + "1.3.2" + ], + "lockfile:/path/to/scorecard-check-osv-e2e/sub-rust-project/Cargo.lock:GHSA-m5pq-gvj9-9vr8,RUSTSEC-2022-0013": [ + "1.5.5" + ] +} diff --git a/internal/output/sarif.go b/internal/output/sarif.go new file mode 100644 index 0000000000..bc1f14e52e --- /dev/null +++ b/internal/output/sarif.go @@ -0,0 +1,123 @@ +package output + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/google/osv-scanner/pkg/models" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/owenrumney/go-sarif/v2/sarif" + "golang.org/x/exp/slices" +) + +// GroupFixedVersions builds the fixed versions for each ID Group +func GroupFixedVersions(flattened []models.VulnerabilityFlattened) map[string][]string { + groupFixedVersions := map[string][]string{} + + // Get the fixed versions indexed by each group of vulnerabilities + // Prepend source path as same vulnerability in two projects should be counted twice + // Remember to sort and compact before displaying later + for _, vf := range flattened { + groupIdx := vf.Source.String() + ":" + vf.GroupInfo.IndexString() + pkg := models.Package{ + Ecosystem: models.Ecosystem(vf.Package.Ecosystem), + Name: vf.Package.Name, + } + groupFixedVersions[groupIdx] = + append(groupFixedVersions[groupIdx], vf.Vulnerability.FixedVersions()[pkg]...) + } + + // Remove duplicates + for k := range groupFixedVersions { + fixedVersions := groupFixedVersions[k] + slices.Sort(fixedVersions) + groupFixedVersions[k] = slices.Compact(fixedVersions) + } + + return groupFixedVersions +} + +// CreateSourceRemediationTable creates a vulnerability table which includes the fixed versions for a specific source file +func CreateSourceRemediationTable(source models.PackageSource, groupFixedVersions map[string][]string) table.Writer { + remediationTable := table.NewWriter() + remediationTable.AppendHeader(table.Row{"Package", "Vulnerability ID", "Current Version", "Fixed Version"}) + + for _, pv := range source.Packages { + for _, group := range pv.Groups { + fixedVersions := groupFixedVersions[source.Source.String()+":"+group.IndexString()] + + vulnIDs := []string{} + for _, id := range group.IDs { + vulnIDs = append(vulnIDs, fmt.Sprintf("https://osv.dev/%s", id)) + } + remediationTable.AppendRow(table.Row{ + pv.Package.Name, + strings.Join(vulnIDs, "\n"), + pv.Package.Version, + strings.Join(fixedVersions, "\n")}) + } + } + + return remediationTable +} + +// PrintSARIFReport prints SARIF output to outputWriter +func PrintSARIFReport(vulnResult *models.VulnerabilityResults, outputWriter io.Writer) error { + report, err := sarif.New(sarif.Version210) + if err != nil { + return err + } + + run := sarif.NewRunWithInformationURI("osv-scanner", "https://github.com/google/osv-scanner") + run.AddRule("vulnerable-packages"). + WithDescription("This manifest file contains one or more vulnerable packages.") + flattened := vulnResult.Flatten() + + // TODO: Also support last affected + groupFixedVersions := GroupFixedVersions(flattened) + workingDir, workingDirErr := os.Getwd() + + for _, source := range vulnResult.Results { + // TODO: Support docker images + + var artifactPath string + if workingDirErr == nil { + artifactPath, err = filepath.Rel(workingDir, source.Source.Path) + if err != nil { + artifactPath = source.Source.Path + } + } else { + artifactPath = source.Source.Path + } + run.AddDistinctArtifact(artifactPath) + + remediationTable := CreateSourceRemediationTable(source, groupFixedVersions) + + renderedTable := remediationTable.Render() + // This is required since the github message rendering is a mixture of + // monospaced font text and markdown. Continuous spaces will be compressed + // down to one space, breaking the table rendering + renderedTable = strings.ReplaceAll(renderedTable, " ", "  ") + run.CreateResultForRule("vulnerable-packages"). + WithLevel("warning"). + WithMessage(sarif.NewMessage().WithText(renderedTable)). + AddLocation( + sarif.NewLocationWithPhysicalLocation( + sarif.NewPhysicalLocation(). + WithArtifactLocation( + sarif.NewSimpleArtifactLocation(artifactPath)))) + } + + report.AddRun(run) + + err = report.PrettyWrite(outputWriter) + if err != nil { + return err + } + fmt.Fprintln(outputWriter) + + return nil +} diff --git a/internal/output/sarif_test.go b/internal/output/sarif_test.go new file mode 100644 index 0000000000..a10bf9455a --- /dev/null +++ b/internal/output/sarif_test.go @@ -0,0 +1,37 @@ +package output + +import ( + "testing" + + "github.com/google/osv-scanner/internal/testutility" + "github.com/google/osv-scanner/pkg/models" +) + +func TestGroupFixedVersions(t *testing.T) { + t.Parallel() + + type args struct { + flattened []models.VulnerabilityFlattened + } + tests := []struct { + name string + args args + wantPath string + }{ + { + name: "", + args: args{ + flattened: testutility.LoadJSONFixture[[]models.VulnerabilityFlattened](t, "fixtures/flattened_vulns.json"), + }, + wantPath: "fixtures/group_fixed_version_output.json", + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := GroupFixedVersions(tt.args.flattened) + testutility.AssertMatchFixtureJSON(t, tt.wantPath, got) + }) + } +} diff --git a/internal/output/table.go b/internal/output/table.go index 18519b15dd..11fc029c5c 100644 --- a/internal/output/table.go +++ b/internal/output/table.go @@ -15,26 +15,23 @@ import ( "github.com/jedib0t/go-pretty/v6/table" "github.com/jedib0t/go-pretty/v6/text" - "golang.org/x/term" ) // PrintTableResults prints the osv scan results into a human friendly table. -func PrintTableResults(vulnResult *models.VulnerabilityResults, outputWriter io.Writer) { +func PrintTableResults(vulnResult *models.VulnerabilityResults, outputWriter io.Writer, terminalWidth int) { outputTable := table.NewWriter() outputTable.SetOutputMirror(outputWriter) outputTable.AppendHeader(table.Row{"OSV URL", "CVSS", "Ecosystem", "Package", "Version", "Source"}) - width, _, err := term.GetSize(int(os.Stdout.Fd())) - isTerminal := false - if err == nil { // If output is a terminal, set max length to width and add styling + + if terminalWidth > 0 { // If output is a terminal, set max length to width and add styling outputTable.SetStyle(table.StyleRounded) outputTable.Style().Color.Row = text.Colors{text.Reset, text.BgHiBlack} outputTable.Style().Color.RowAlternate = text.Colors{text.Reset, text.BgBlack} outputTable.Style().Options.DoNotColorBordersAndSeparators = true - outputTable.SetAllowedRowLength(width) - isTerminal = true + outputTable.SetAllowedRowLength(terminalWidth) } // Otherwise use default ascii (e.g. getting piped to a file) - outputTable = tableBuilder(outputTable, vulnResult, isTerminal) + outputTable = tableBuilder(outputTable, vulnResult, terminalWidth > 0) if outputTable.Length() == 0 { return @@ -128,7 +125,7 @@ func tableBuilderInner(vulnResult *models.VulnerabilityResults, addStyling bool, outputSeverities = append(outputSeverities, outputSeverity) } } - outputRow = append(outputRow, strings.Join(outputSeverities, ",\n")) + outputRow = append(outputRow, strings.Join(outputSeverities, "\n")) if pkg.Package.Ecosystem == "GIT" { outputRow = append(outputRow, "GIT", pkg.Package.Version, pkg.Package.Version) diff --git a/pkg/grouper/grouper.go b/pkg/grouper/grouper.go index 942331f894..297404217a 100644 --- a/pkg/grouper/grouper.go +++ b/pkg/grouper/grouper.go @@ -61,6 +61,8 @@ func Group(vulns []IDAliases) []models.GroupInfo { result := make([]models.GroupInfo, 0, len(sortedKeys)) for _, key := range sortedKeys { + // Sort the strings so they are always in the same order + sort.Strings(extractedGroups[key]) result = append(result, models.GroupInfo{IDs: extractedGroups[key]}) } diff --git a/pkg/grouper/grouper_test.go b/pkg/grouper/grouper_test.go index f852c8b3c7..be06f14744 100644 --- a/pkg/grouper/grouper_test.go +++ b/pkg/grouper/grouper_test.go @@ -103,10 +103,10 @@ func TestGroup(t *testing.T) { IDs: []string{v8.ID}, }, { - IDs: []string{v2.ID, v1.ID, v3.ID}, + IDs: []string{v1.ID, v2.ID, v3.ID}, // Deterministic order }, { - IDs: []string{v5.ID, v4.ID, v6.ID}, + IDs: []string{v4.ID, v5.ID, v6.ID}, }, { IDs: []string{v7.ID}, diff --git a/pkg/models/results.go b/pkg/models/results.go index 1b40606936..dbb15203a4 100644 --- a/pkg/models/results.go +++ b/pkg/models/results.go @@ -1,6 +1,8 @@ package models import ( + "strings" + "golang.org/x/exp/slices" ) @@ -61,6 +63,7 @@ type PackageVulns struct { } type GroupInfo struct { + // IDs expected to be sorted in alphanumeric order IDs []string `json:"ids"` // Map of Vulnerability IDs to AnalysisInfo ExperimentalAnalysis map[string]AnalysisInfo `json:"experimentalAnalysis,omitempty"` @@ -82,6 +85,29 @@ func (groupInfo *GroupInfo) IsCalled() bool { return false } +func (groupInfo *GroupInfo) IndexString() string { + // Assumes IDs is sorted + return strings.Join(groupInfo.IDs, ",") +} + +// FixedVersions returns a map of fixed versions for each package, or a map of empty slices if no fixed versions are available +func (v *Vulnerability) FixedVersions() map[Package][]string { + output := map[Package][]string{} + for _, a := range v.Affected { + packageKey := a.Package + packageKey.Purl = "" + for _, r := range a.Ranges { + for _, e := range r.Events { + if e.Fixed != "" { + output[packageKey] = append(output[packageKey], e.Fixed) + } + } + } + } + + return output +} + type AnalysisInfo struct { Called bool `json:"called"` } diff --git a/pkg/reporter/format.go b/pkg/reporter/format.go new file mode 100644 index 0000000000..ab02e404b0 --- /dev/null +++ b/pkg/reporter/format.go @@ -0,0 +1,29 @@ +package reporter + +import ( + "fmt" + "io" +) + +var format = []string{"table", "json", "markdown", "sarif"} + +func Format() []string { + return format +} + +// New returns an implementation of the reporter interface depending on the format passed in +// set terminalWidth as 0 to indicate the output is not a terminal +func New(format string, stdout, stderr io.Writer, terminalWidth int) (Reporter, error) { + switch format { + case "json": + return NewJSONReporter(stdout, stderr), nil + case "table": + return NewTableReporter(stdout, stderr, false, terminalWidth), nil + case "markdown": + return NewTableReporter(stdout, stderr, true, terminalWidth), nil + case "sarif": + return NewSarifReporter(stdout, stderr), nil + default: + return nil, fmt.Errorf("%v is not a valid format", format) + } +} diff --git a/pkg/reporter/format_test.go b/pkg/reporter/format_test.go new file mode 100644 index 0000000000..027356c709 --- /dev/null +++ b/pkg/reporter/format_test.go @@ -0,0 +1,22 @@ +package reporter_test + +import ( + "bytes" + "testing" + + "github.com/google/osv-scanner/pkg/reporter" +) + +func TestGetReporter(t *testing.T) { + t.Parallel() + + for _, format := range reporter.Format() { + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + + _, err := reporter.New(format, stdout, stderr, 0) + if err != nil { + t.Errorf("Reporter for '%s' format not implemented", format) + } + } +} diff --git a/pkg/reporter/sarif_reporter.go b/pkg/reporter/sarif_reporter.go new file mode 100644 index 0000000000..825237b03a --- /dev/null +++ b/pkg/reporter/sarif_reporter.go @@ -0,0 +1,40 @@ +package reporter + +import ( + "fmt" + "io" + + "github.com/google/osv-scanner/internal/output" + "github.com/google/osv-scanner/pkg/models" +) + +type SARIFReporter struct { + hasPrintedError bool + stdout io.Writer + stderr io.Writer +} + +func NewSarifReporter(stdout io.Writer, stderr io.Writer) *SARIFReporter { + return &SARIFReporter{ + stdout: stdout, + stderr: stderr, + hasPrintedError: false, + } +} + +func (r *SARIFReporter) PrintError(msg string) { + fmt.Fprint(r.stderr, msg) + r.hasPrintedError = true +} + +func (r *SARIFReporter) HasPrintedError() bool { + return r.hasPrintedError +} + +func (r *SARIFReporter) PrintText(msg string) { + fmt.Fprint(r.stderr, msg) +} + +func (r *SARIFReporter) PrintResult(vulnResult *models.VulnerabilityResults) error { + return output.PrintSARIFReport(vulnResult, r.stdout) +} diff --git a/pkg/reporter/table_reporter.go b/pkg/reporter/table_reporter.go index 69d07db4f7..dde1436916 100644 --- a/pkg/reporter/table_reporter.go +++ b/pkg/reporter/table_reporter.go @@ -13,14 +13,17 @@ type TableReporter struct { stdout io.Writer stderr io.Writer markdown bool + // 0 indicates not a terminal output + terminalWidth int } -func NewTableReporter(stdout io.Writer, stderr io.Writer, markdown bool) *TableReporter { +func NewTableReporter(stdout io.Writer, stderr io.Writer, markdown bool, terminalWidth int) *TableReporter { return &TableReporter{ stdout: stdout, stderr: stderr, hasPrintedError: false, markdown: markdown, + terminalWidth: terminalWidth, } } @@ -46,7 +49,7 @@ func (r *TableReporter) PrintResult(vulnResult *models.VulnerabilityResults) err if r.markdown { output.PrintMarkdownTableResults(vulnResult, r.stdout) } else { - output.PrintTableResults(vulnResult, r.stdout) + output.PrintTableResults(vulnResult, r.stdout, r.terminalWidth) } return nil