diff --git a/.github/workflows/osv-scanner-pr.yml b/.github/workflows/osv-scanner-pr.yml new file mode 100644 index 00000000000..e5181a986c3 --- /dev/null +++ b/.github/workflows/osv-scanner-pr.yml @@ -0,0 +1,58 @@ +# 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 + +# env: +# # Generator +# BUILDER_BINARY: slsa-generator-generic-linux-amd64 # Name of the binary in the release assets. +# BUILDER_DIR: internal/builders/generic # Source directory if we compile the builder. + +# defaults: +# run: +# shell: bash + +on: + workflow_call: + +jobs: + scan-pr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + persist-credentials: false + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v37 + + # To compare changes between the current commit and the last pushed remote commit set `since_last_remote_commit: true`. e.g + # with: + # since_last_remote_commit: true + + - name: List all changed files + run: | + echo ${{ steps.changed-files.outputs.all_changed_files }} + echo ------------ + for file in ${{ steps.changed-files.outputs.all_changed_files }}; do + echo "$file was changed" + done + - name: "Run scanner" + uses: ./ # Uses ./action.yaml + with: + results-format: sarif + results-file: results.sarif + to-scan: ${{ steps.changed-files.outputs.all_changed_files }} + diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml new file mode 100644 index 00000000000..89287e442f5 --- /dev/null +++ b/.github/workflows/osv-scanner.yml @@ -0,0 +1,75 @@ +# 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: + push: + branches: [ main ] + 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-pr.yml" + # scan: + # name: OSV-Scanner scan + # runs-on: ubuntu-latest + # permissions: + # # Needed to upload the results to code-scanning dashboard. + # security-events: write + # # Needed to publish results and get a badge (see publish_results below). + # id-token: write + # # Uncomment the permissions below if installing in a private repository. + # # contents: read + # # actions: read + + # steps: + # - name: "Checkout code" + # uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + # with: + # persist-credentials: false + + # - name: "Run scanner" + # uses: ./ # Uses ./action.yaml + # with: + # results-format: sarif + # results-file: results.sarif + # to-scan: |- + # ./ + # ./cmd/osv-scanner/fixtures/locks-many/ + + + # # 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/action.dockerfile b/action.dockerfile new file mode 100644 index 00000000000..c1985b6c353 --- /dev/null +++ b/action.dockerfile @@ -0,0 +1,42 @@ +# Copyright 2022 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/ + +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 ./exit_code_redirect.sh ./ + +ENV PATH="${PATH}:/root" + +ENTRYPOINT ["bash", "/root/exit_code_redirect.sh"] diff --git a/action.yml b/action.yml index 5d8ded885a7..63cab7c50ab 100644 --- a/action.yml +++ b/action.yml @@ -3,19 +3,26 @@ 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' + description: 'Directories to scan' + required: false + default: "./" + results-file: + description: 'OUTPUT: Path to file to store results' required: true - arch: - description: 'osv-scanner architecture' + results-format: + description: 'Output result format' + required: false + default: 'sarif' + recursive-search: + description: 'Recursively scan though subdirectories' required: false - default: 'amd64' + default: false runs: using: 'docker' - image: 'ghcr.io/google/osv-scanner:${{ inputs.version }}-${{ inputs.arch }}' + image: 'action.dockerfile' args: - '--skip-git' + - '--output=${{ inputs.results-file }}' + - '--format=${{ inputs.results-format }}' + - '--recursive=${{ inputs.recursive-search}}' - ${{ inputs.to-scan }} 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 00000000000..5cfb2dc2428 --- /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 00000000000..e3a2d44973c --- /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 e69de29bb2d..9a13cb6b4c6 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 746825051f1..23cfaa5abae 100644 --- a/cmd/osv-scanner/main.go +++ b/cmd/osv-scanner/main.go @@ -1,14 +1,17 @@ package main import ( + "bytes" "errors" "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" "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,15 @@ 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") + outputBuffer := &bytes.Buffer{} + if outputPath != "" { + stdout = outputBuffer + } + + var err error + if r, err = reporter.GetReporter(format, stdout, stderr, outputPath != ""); err != nil { + return err } vulnResult, err := osvscanner.DoScan(osvscanner.ScannerActions{ @@ -147,13 +151,26 @@ func run(args []string, stdout, stderr io.Writer) int { return fmt.Errorf("failed to write output: %w", errPrint) } + if outputPath != "" { + file, err := os.Create(outputPath) + if err != nil { + return fmt.Errorf("failed to create output file: %w", err) + } + + _, err = file.Write(outputBuffer.Bytes()) + if err != nil { + return fmt.Errorf("failed to write to output file: %w", err) + } + } + + // 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 5c245662224..e19ef07d9c6 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 00000000000..5c4023eea8c --- /dev/null +++ b/exit_code_redirect.sh @@ -0,0 +1,56 @@ +#!/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' ' ')) + +# for var in "${split_args[@]}" +# do +# echo each $var +# done + +# echo "${split_args[1]}" +# 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 2b8d94be3e3..e13b2e2ec09 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 41f91261552..b10edd374cd 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= @@ -41,6 +42,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= @@ -64,6 +68,10 @@ 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 h1:QNObu6YX1igyFKhdzd7vgzmw7XsWN3/6NMGuDzBgXmE= +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= @@ -95,6 +103,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= @@ -102,11 +111,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= @@ -119,7 +131,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.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/mod v0.11.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= @@ -155,7 +169,9 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= 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= @@ -170,7 +186,9 @@ golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM golang.org/x/vuln v0.0.0-20230303230808-d3042fecc4e3 h1:9GJsAwSzB/ztwMwsEm3ihUgCXHCULbNsubxqIrdKa44= golang.org/x/vuln v0.0.0-20230303230808-d3042fecc4e3/go.mod h1:LTLnfk/dpXDNKsX6aCg/cI4LyCVnTyrQhgV/yLJuly0= 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/output/fixtures/flattened_vulns.json b/internal/output/fixtures/flattened_vulns.json new file mode 100644 index 00000000000..96f023d6d76 --- /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 00000000000..ac488cb7458 --- /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 00000000000..1ed720a009d --- /dev/null +++ b/internal/output/sarif.go @@ -0,0 +1,122 @@ +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() + + 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 00000000000..ec03cab9a32 --- /dev/null +++ b/internal/output/sarif_test.go @@ -0,0 +1,36 @@ +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 + want map[string][]string + }{ + { + name: "", + args: args{ + flattened: testutility.LoadJSONFixture[[]models.VulnerabilityFlattened](t, "fixtures/flattened_vulns.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, "fixtures/group_fixed_version_output.json", got) + }) + } +} diff --git a/internal/output/table.go b/internal/output/table.go index 18519b15dda..11fc029c5cc 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 942331f8943..297404217aa 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 f852c8b3c7f..be06f147446 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 1b40606936d..c78d755f5de 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 list of fixed versions, or an empty slice if no fixed version is available +func (v *Vulnerability) FixedVersions() map[Package][]string { + output := map[Package][]string{} + for _, a := range v.Affected { + cleanedPackaged := a.Package + cleanedPackaged.Purl = "" + for _, r := range a.Ranges { + for _, e := range r.Events { + if e.Fixed != "" { + output[cleanedPackaged] = append(output[cleanedPackaged], 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 00000000000..78a729785c0 --- /dev/null +++ b/pkg/reporter/format.go @@ -0,0 +1,41 @@ +package reporter + +import ( + "fmt" + "io" + "os" + + "golang.org/x/term" +) + +var format = []string{"table", "json", "markdown", "sarif"} + +func Format() []string { + return format +} + +func GetReporter(format string, stdout, stderr io.Writer, fileOutput bool) (Reporter, error) { + var width int + if !fileOutput { + var err error + width, _, err = term.GetSize(int(os.Stdout.Fd())) + if err != nil { // If output is not a terminal, + width = 0 + } + } else { // Output is a file + width = 0 + } + + switch format { + case "json": + return NewJSONReporter(stdout, stderr), nil + case "table": + return NewTableReporter(stdout, stderr, false, width), nil + case "markdown": + return NewTableReporter(stdout, stderr, true, width), 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 00000000000..9c2d4f0f098 --- /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.GetReporter(format, stdout, stderr, true) + 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 00000000000..825237b03a0 --- /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 69d07db4f78..dde14369162 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