diff --git a/.codecov.yml b/.codecov.yml index b49dfb9e315..97cae9da52b 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -23,16 +23,15 @@ coverage: status: project: default: - enabled: true - # allowed to drop coverage and still result in a "success" commit status - threshold: null + informational: true if_not_found: success if_no_uploads: success if_ci_failed: error patch: default: - enabled: true - threshold: 90% + # patch coverage should be within 10% of existing coverage + target: auto + threshold: 10% if_not_found: success if_no_uploads: success if_ci_failed: error diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 73235106ba1..c7a6fa7bbae 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -57,7 +57,7 @@ jobs: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Checkout repository - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/depsreview.yml b/.github/workflows/depsreview.yml index d5e5dd9774a..bc90b255cf5 100644 --- a/.github/workflows/depsreview.yml +++ b/.github/workflows/depsreview.yml @@ -22,6 +22,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: 'Dependency Review' - uses: actions/dependency-review-action@f6fff72a3217f580d5afd49a46826795305b63c7 + uses: actions/dependency-review-action@6c5ccdad469c9f8a2996bfecaec55a631a347034 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 18ab404440b..4084f1d18be 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -35,12 +35,12 @@ jobs: docs_only: ${{ steps.docs_only_check.outputs.docs_only }} steps: - name: Check out code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 #v4.1.0 with: fetch-depth: 2 # needed to diff changed files - id: files name: Get changed files - uses: tj-actions/changed-files@48566bbcc22ceb7c5809ebdd27377309f2c3de8c #v39.0.0 + uses: tj-actions/changed-files@41960309398d165631f08c5df47a11147e14712b #v39.1.2 with: files_ignore: '**.md' - id: docs_only_check @@ -75,7 +75,7 @@ jobs: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Clone the code if: (needs.docs_only_check.outputs.docs_only != 'true') - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Setup Go # needed for some of the Makefile evaluations, even if building happens in Docker if: (needs.docs_only_check.outputs.docs_only != 'true') uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 diff --git a/.github/workflows/gitlab.yml b/.github/workflows/gitlab.yml index 70c6f9d609d..e54d5d77683 100644 --- a/.github/workflows/gitlab.yml +++ b/.github/workflows/gitlab.yml @@ -37,7 +37,7 @@ jobs: with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Clone the code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} # head SHA if PR, else fallback to push SHA - name: Setup Go @@ -52,7 +52,7 @@ jobs: echo "go-mod=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT" - name: Cache builds # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 #v3.3.1 + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2 with: path: | ${{ steps.go-cache-paths.outputs.go-build }} diff --git a/.github/workflows/goreleaser.yaml b/.github/workflows/goreleaser.yaml index 4ed2ca708fa..c607071baff 100644 --- a/.github/workflows/goreleaser.yaml +++ b/.github/workflows/goreleaser.yaml @@ -39,7 +39,7 @@ jobs: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Checkout - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 with: fetch-depth: 0 - name: Set up Go @@ -52,7 +52,7 @@ jobs: run: echo "version_flags=$(./scripts/version-ldflags)" >> "$GITHUB_OUTPUT" - name: Run GoReleaser id: run-goreleaser - uses: goreleaser/goreleaser-action@5fdedb94abba051217030cc86d4523cf3f02243d # v2.5.0 + uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v2.5.0 with: version: latest args: release --rm-dist diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 88389c5e672..67137cb8692 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -48,7 +48,7 @@ jobs: with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Clone the code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 with: ref: ${{ github.event.pull_request.head.sha }} - name: Setup Go @@ -63,7 +63,7 @@ jobs: echo "go-mod=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT" - name: Cache builds # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 #v3.3.1 + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2 with: path: | ${{ steps.go-cache-paths.outputs.go-build }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9256167a6d5..01140ca44f0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,7 +22,7 @@ jobs: - uses: step-security/harden-runner@8ca2b8b2ece13480cda6dacd3511b49857a23c09 # v2.5.1 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 36a9f2cb969..073f2a9d4f7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -41,7 +41,7 @@ jobs: with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Clone the code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Setup Go uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 with: @@ -54,7 +54,7 @@ jobs: echo "go-mod=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT" - name: Cache builds # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 #v3.3.1 + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 #v3.3.2 with: path: | ${{ steps.go-cache-paths.outputs.go-build }} @@ -106,7 +106,7 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Cache builds # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 with: path: | ~/go/pkg/mod @@ -117,7 +117,7 @@ jobs: restore-keys: | ${{ runner.os }}-go- - name: Clone the code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 with: fetch-depth: 0 - name: Setup Go @@ -147,7 +147,7 @@ jobs: with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Clone the code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Setup Go uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 with: @@ -182,7 +182,7 @@ jobs: version: ${{ env.PROTOC_VERSION }} repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Clone the code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 with: fetch-depth: 0 - name: Setup Go @@ -226,7 +226,7 @@ jobs: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Cache builds # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 with: path: | ~/go/pkg/mod @@ -237,7 +237,7 @@ jobs: restore-keys: | ${{ runner.os }}-go- - name: Clone the code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Setup Go uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 with: @@ -266,7 +266,7 @@ jobs: - name: Cache builds # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 with: path: | ~/go/pkg/mod @@ -277,7 +277,7 @@ jobs: restore-keys: | ${{ runner.os }}-go- - name: Clone the code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 with: fetch-depth: 0 - name: Setup Go @@ -313,7 +313,7 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Cache builds # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1 + uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 with: path: | ~/go/pkg/mod @@ -324,7 +324,7 @@ jobs: restore-keys: | ${{ runner.os }}-go- - name: Clone the code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 with: fetch-depth: 0 - name: Setup Go @@ -359,7 +359,7 @@ jobs: version: ${{ env.PROTOC_VERSION }} repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Clone the code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 with: fetch-depth: 0 - name: Setup Go @@ -388,7 +388,7 @@ jobs: with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v2.2.0 with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/publishimage.yml b/.github/workflows/publishimage.yml index 1efae292fcc..e6f510b03bf 100644 --- a/.github/workflows/publishimage.yml +++ b/.github/workflows/publishimage.yml @@ -40,7 +40,7 @@ jobs: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Clone the code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 with: fetch-depth: 0 - name: Setup Go diff --git a/.github/workflows/scorecard-analysis.yml b/.github/workflows/scorecard-analysis.yml index 153d5ede72a..1c992d48fcf 100644 --- a/.github/workflows/scorecard-analysis.yml +++ b/.github/workflows/scorecard-analysis.yml @@ -22,7 +22,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: "Run analysis" uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0 @@ -40,7 +40,7 @@ jobs: # https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts # Optional. - name: "Upload artifact" - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3 + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/slsa-goreleaser.yml b/.github/workflows/slsa-goreleaser.yml index 6a7fe8b8e8c..133e9ec360d 100644 --- a/.github/workflows/slsa-goreleaser.yml +++ b/.github/workflows/slsa-goreleaser.yml @@ -19,7 +19,7 @@ jobs: go-binary-name: ${{ steps.build.outputs.go-binary-name }} steps: - id: checkout - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 with: fetch-depth: 0 - id: ldflags diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 8c228e59a2a..b3302c6260d 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -34,7 +34,7 @@ jobs: - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v3.0.18 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'Stale issue message' + stale-issue-message: 'This issue is stale because it has been open for 60 days with no activity.' stale-pr-message: 'Stale pull request message' stale-issue-label: 'no-issue-activity' exempt-issue-labels: 'priority,bug,good first issue' @@ -43,3 +43,5 @@ jobs: days-before-pr-stale: '10' days-before-pr-close: '20' days-before-issue-stale: '60' + days-before-issue-close: -1 + operations-per-run: '100' diff --git a/README.md b/README.md index c5b886ca3ad..e3a1af9ca40 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # OpenSSF Scorecard [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/ossf/scorecard/badge)](https://securityscorecards.dev/viewer/?uri=github.com/ossf/scorecard) -[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/5621/badge)](https://bestpractices.coreinfrastructure.org/projects/5621) +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/5621/badge)](https://www.bestpractices.dev/projects/5621) ![build](https://github.com/ossf/scorecard/workflows/build/badge.svg?branch=main) ![CodeQL](https://github.com/ossf/scorecard/workflows/CodeQL/badge.svg?branch=main) [![Go Reference](https://pkg.go.dev/badge/github.com/ossf/scorecard/v4.svg)](https://pkg.go.dev/github.com/ossf/scorecard/v4) @@ -16,6 +16,7 @@ - [What Is Scorecard?](#what-is-scorecard) - [Prominent Scorecard Users](#prominent-scorecard-users) +- [View a Project's Score](#view-a-projects-score) - [Scorecard's Public Data](#public-data) ## Using Scorecard @@ -91,6 +92,17 @@ metrics. Prominent projects that use Scorecard include: - [sos.dev](https://sos.dev) - [deps.dev](https://deps.dev) +### View a Project's Score + +To see scores for projects regually scanned by Scorecard, navigate to the webviewer, replacing the placeholder text with the platform, user/org, and repository name: +https://securityscorecards.dev/viewer/?uri=.com//. + +For example: + - [https://securityscorecards.dev/viewer/?uri=github.com/ossf/scorecard](https://securityscorecards.dev/viewer/?uri=github.com/ossf/scorecard) + - [https://securityscorecards.dev/viewer/?uri=gitlab.com/fdroid/fdroidclient](https://securityscorecards.dev/viewer/?uri=gitlab.com/fdroid/fdroidclient) + +To view scores for projects not included in the webviewer, use the [Scorecard CLI](#scorecard-command-line-interface). + ### Public Data We run a weekly Scorecard scan of the 1 million most critical open source @@ -472,7 +484,7 @@ Name | Description | Risk Level | Token Req [Binary-Artifacts](docs/checks.md#binary-artifacts) | Is the project free of checked-in binaries? | High | PAT, GITHUB_TOKEN | Supported | [Branch-Protection](docs/checks.md#branch-protection) | Does the project use [Branch Protection](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/about-protected-branches) ? | High | PAT (`repo` or `repo> public_repo`), GITHUB_TOKEN | Supported (see notes) | certain settings are only supported with a maintainer PAT [CI-Tests](docs/checks.md#ci-tests) | Does the project run tests in CI, e.g. [GitHub Actions](https://docs.github.com/en/free-pro-team@latest/actions), [Prow](https://github.com/kubernetes/test-infra/tree/master/prow)? | Low | PAT, GITHUB_TOKEN | Supported -[CII-Best-Practices](docs/checks.md#cii-best-practices) | Has the project earned an [OpenSSF (formerly CII) Best Practices Badge](https://bestpractices.coreinfrastructure.org) at the passing, silver, or gold level? | Low | PAT, GITHUB_TOKEN | Validating | +[CII-Best-Practices](docs/checks.md#cii-best-practices) | Has the project earned an [OpenSSF (formerly CII) Best Practices Badge](https://www.bestpractices.dev) at the passing, silver, or gold level? | Low | PAT, GITHUB_TOKEN | Validating | [Code-Review](docs/checks.md#code-review) | Does the project practice code review before code is merged? | High | PAT, GITHUB_TOKEN | Validating | [Contributors](docs/checks.md#contributors) | Does the project have contributors from at least two different organizations? | Low | PAT, GITHUB_TOKEN | Validating | [Dangerous-Workflow](docs/checks.md#dangerous-workflow) | Does the project avoid dangerous coding patterns in GitHub Action workflows? | Critical | PAT, GITHUB_TOKEN | Unsupported | diff --git a/checker/check_result.go b/checker/check_result.go index 730db8ac644..00fc9172da1 100644 --- a/checker/check_result.go +++ b/checker/check_result.go @@ -16,6 +16,7 @@ package checker import ( + "errors" "fmt" "math" @@ -50,6 +51,10 @@ const ( DetailDebug ) +// errSuccessTotal indicates a runtime error because number of success cases should +// be smaller than the total cases to create a proportional score. +var errSuccessTotal = errors.New("unexpected number of success is higher than total") + // CheckResult captures result from a check run. // //nolint:govet @@ -88,6 +93,14 @@ type LogMessage struct { Remediation *rule.Remediation // Remediation information, if any. } +// ProportionalScoreWeighted is a structure that contains +// the fields to calculate weighted proportional scores. +type ProportionalScoreWeighted struct { + Success int + Total int + Weight int +} + // CreateProportionalScore creates a proportional score. func CreateProportionalScore(success, total int) int { if total == 0 { @@ -97,6 +110,40 @@ func CreateProportionalScore(success, total int) int { return int(math.Min(float64(MaxResultScore*success/total), float64(MaxResultScore))) } +// CreateProportionalScoreWeighted creates the proportional score +// between multiple successes over the total, but some proportions +// are worth more. +func CreateProportionalScoreWeighted(scores ...ProportionalScoreWeighted) (int, error) { + var ws, wt int + allWeightsZero := true + noScoreGroups := true + for _, score := range scores { + if score.Success > score.Total { + return InconclusiveResultScore, fmt.Errorf("%w: %d, %d", errSuccessTotal, score.Success, score.Total) + } + if score.Total == 0 { + continue // Group with 0 total, does not count for score + } + noScoreGroups = false + if score.Weight != 0 { + allWeightsZero = false + } + // Group with zero weight, adds nothing to the score + + ws += score.Success * score.Weight + wt += score.Total * score.Weight + } + if noScoreGroups { + return InconclusiveResultScore, nil + } + // If has score groups but no groups matter to the score, result in max score + if allWeightsZero { + return MaxResultScore, nil + } + + return int(math.Min(float64(MaxResultScore*ws/wt), float64(MaxResultScore))), nil +} + // AggregateScores adds up all scores // and normalizes the result. // Each score contributes equally. @@ -195,7 +242,7 @@ func CreateRuntimeErrorResult(name string, e error) CheckResult { } // LogFindings logs the list of findings. -func LogFindings(findings []finding.Finding, dl DetailLogger) error { +func LogFindings(findings []finding.Finding, dl DetailLogger) { for i := range findings { f := &findings[i] switch f.Outcome { @@ -213,6 +260,4 @@ func LogFindings(findings []finding.Finding, dl DetailLogger) error { }) } } - - return nil } diff --git a/checker/check_result_test.go b/checker/check_result_test.go index 291f0696cde..1ec48850340 100644 --- a/checker/check_result_test.go +++ b/checker/check_result_test.go @@ -139,6 +139,273 @@ func TestCreateProportionalScore(t *testing.T) { } } +func TestCreateProportionalScoreWeighted(t *testing.T) { + t.Parallel() + type want struct { + score int + err bool + } + tests := []struct { + name string + scores []ProportionalScoreWeighted + want want + }{ + { + name: "max result with 1 group and normal weight", + scores: []ProportionalScoreWeighted{ + { + Success: 1, + Total: 1, + Weight: 10, + }, + }, + want: want{ + score: 10, + }, + }, + { + name: "min result with 1 group and normal weight", + scores: []ProportionalScoreWeighted{ + { + Success: 0, + Total: 1, + Weight: 10, + }, + }, + want: want{ + score: 0, + }, + }, + { + name: "partial result with 1 group and normal weight", + scores: []ProportionalScoreWeighted{ + { + Success: 2, + Total: 10, + Weight: 10, + }, + }, + want: want{ + score: 2, + }, + }, + { + name: "partial result with 2 groups and normal weights", + scores: []ProportionalScoreWeighted{ + { + Success: 2, + Total: 10, + Weight: 10, + }, + { + Success: 8, + Total: 10, + Weight: 10, + }, + }, + want: want{ + score: 5, + }, + }, + { + name: "partial result with 2 groups and odd weights", + scores: []ProportionalScoreWeighted{ + { + Success: 2, + Total: 10, + Weight: 8, + }, + { + Success: 8, + Total: 10, + Weight: 2, + }, + }, + want: want{ + score: 3, + }, + }, + { + name: "all groups with 0 weight, no groups matter for the score, results in max score", + scores: []ProportionalScoreWeighted{ + { + Success: 1, + Total: 1, + Weight: 0, + }, + { + Success: 1, + Total: 1, + Weight: 0, + }, + }, + want: want{ + score: 10, + }, + }, + { + name: "not all groups with 0 weight, only groups with weight matter to the score", + scores: []ProportionalScoreWeighted{ + { + Success: 1, + Total: 1, + Weight: 0, + }, + { + Success: 2, + Total: 10, + Weight: 8, + }, + { + Success: 8, + Total: 10, + Weight: 2, + }, + }, + want: want{ + score: 3, + }, + }, + { + name: "no total, results in inconclusive score", + scores: []ProportionalScoreWeighted{ + { + Success: 0, + Total: 0, + Weight: 10, + }, + }, + want: want{ + score: -1, + }, + }, + { + name: "some groups with 0 total, only groups with total matter to the score", + scores: []ProportionalScoreWeighted{ + { + Success: 0, + Total: 0, + Weight: 10, + }, + { + Success: 2, + Total: 10, + Weight: 10, + }, + }, + want: want{ + score: 2, + }, + }, + { + name: "any group with number of successes higher than total, results in inconclusive score and error", + scores: []ProportionalScoreWeighted{ + { + Success: 1, + Total: 0, + Weight: 10, + }, + }, + want: want{ + score: -1, + err: true, + }, + }, + { + name: "only groups with weight and total matter to the score", + scores: []ProportionalScoreWeighted{ + { + Success: 1, + Total: 1, + Weight: 0, + }, + { + Success: 0, + Total: 0, + Weight: 10, + }, + { + Success: 2, + Total: 10, + Weight: 8, + }, + { + Success: 8, + Total: 10, + Weight: 2, + }, + }, + want: want{ + score: 3, + }, + }, + { + name: "only groups with weight and total matter to the score but no groups have success, results in min score", + scores: []ProportionalScoreWeighted{ + { + Success: 1, + Total: 1, + Weight: 0, + }, + { + Success: 0, + Total: 0, + Weight: 10, + }, + { + Success: 0, + Total: 10, + Weight: 8, + }, + { + Success: 0, + Total: 10, + Weight: 2, + }, + }, + want: want{ + score: 0, + }, + }, + { + name: "group with 0 weight counts as max score and group with 0 total does not count", + scores: []ProportionalScoreWeighted{ + { + Success: 2, + Total: 8, + Weight: 0, + }, + { + Success: 0, + Total: 0, + Weight: 10, + }, + }, + want: want{ + score: 10, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := CreateProportionalScoreWeighted(tt.scores...) + if err != nil && !tt.want.err { + t.Errorf("CreateProportionalScoreWeighted unexpected error '%v'", err) + t.Fail() + } + if err == nil && tt.want.err { + t.Errorf("CreateProportionalScoreWeighted expected error and got none") + t.Fail() + } + if got != tt.want.score { + t.Errorf("CreateProportionalScoreWeighted() = %v, want %v", got, tt.want.score) + } + }) + } +} + func TestNormalizeReason(t *testing.T) { t.Parallel() type args struct { diff --git a/checker/raw_result.go b/checker/raw_result.go index 2acaee17a47..e8834afe0e8 100644 --- a/checker/raw_result.go +++ b/checker/raw_result.go @@ -121,6 +121,7 @@ type Dependency struct { PinnedAt *string Location *File Msg *string // Only for debug messages. + Pinned *bool Type DependencyUseType } diff --git a/checks/branch_protection_test.go b/checks/branch_protection_test.go index 929c20768c8..035120a1486 100644 --- a/checks/branch_protection_test.go +++ b/checks/branch_protection_test.go @@ -134,7 +134,7 @@ func TestReleaseAndDevBranchProtected(t *testing.T) { name: "Only development branch", expected: scut.TestReturn{ Error: nil, - Score: 2, + Score: 3, NumberOfWarn: 7, NumberOfInfo: 2, NumberOfDebug: 0, @@ -173,7 +173,7 @@ func TestReleaseAndDevBranchProtected(t *testing.T) { name: "Take worst of release and development", expected: scut.TestReturn{ Error: nil, - Score: 2, + Score: 4, NumberOfWarn: 9, NumberOfInfo: 10, NumberOfDebug: 0, @@ -285,7 +285,7 @@ func TestReleaseAndDevBranchProtected(t *testing.T) { name: "Ignore a non-branch targetcommitish", expected: scut.TestReturn{ Error: nil, - Score: 2, + Score: 3, NumberOfWarn: 7, NumberOfInfo: 2, NumberOfDebug: 0, diff --git a/checks/dependency_update_tool.go b/checks/dependency_update_tool.go index 1222c6e0dd2..b010511ad7b 100644 --- a/checks/dependency_update_tool.go +++ b/checks/dependency_update_tool.go @@ -20,6 +20,7 @@ import ( "github.com/ossf/scorecard/v4/checks/raw" sce "github.com/ossf/scorecard/v4/errors" "github.com/ossf/scorecard/v4/probes" + "github.com/ossf/scorecard/v4/probes/zrunner" ) // CheckDependencyUpdateTool is the exported name for Automatic-Depdendency-Update. @@ -49,12 +50,12 @@ func DependencyUpdateTool(c *checker.CheckRequest) checker.CheckResult { pRawResults.DependencyUpdateToolResults = rawData // Evaluate the probes. - findings, err := evaluateProbes(c, pRawResults, probes.DependencyToolUpdates) + findings, err := zrunner.Run(pRawResults, probes.DependencyToolUpdates) if err != nil { e := sce.WithMessage(sce.ErrScorecardInternal, err.Error()) return checker.CreateRuntimeErrorResult(CheckDependencyUpdateTool, e) } // Return the score evaluation. - return evaluation.DependencyUpdateTool(CheckDependencyUpdateTool, findings) + return evaluation.DependencyUpdateTool(CheckDependencyUpdateTool, findings, c.Dlogger) } diff --git a/checks/dependency_update_tool_test.go b/checks/dependency_update_tool_test.go index 7ec379667ea..6598499c99c 100644 --- a/checks/dependency_update_tool_test.go +++ b/checks/dependency_update_tool_test.go @@ -51,7 +51,7 @@ func TestDependencyUpdateTool(t *testing.T) { CallSearchCommits: 0, expected: scut.TestReturn{ NumberOfInfo: 1, - NumberOfWarn: 3, + NumberOfWarn: 0, Score: 10, }, }, @@ -64,7 +64,7 @@ func TestDependencyUpdateTool(t *testing.T) { CallSearchCommits: 0, expected: scut.TestReturn{ NumberOfInfo: 1, - NumberOfWarn: 3, + NumberOfWarn: 0, Score: 10, }, }, @@ -103,7 +103,7 @@ func TestDependencyUpdateTool(t *testing.T) { CallSearchCommits: 1, expected: scut.TestReturn{ NumberOfInfo: 1, - NumberOfWarn: 3, + NumberOfWarn: 0, Score: 10, }, }, @@ -118,7 +118,7 @@ func TestDependencyUpdateTool(t *testing.T) { CallSearchCommits: 1, expected: scut.TestReturn{ NumberOfInfo: 1, - NumberOfWarn: 3, + NumberOfWarn: 0, Score: 10, }, }, @@ -136,7 +136,7 @@ func TestDependencyUpdateTool(t *testing.T) { CallSearchCommits: 1, expected: scut.TestReturn{ NumberOfInfo: 1, - NumberOfWarn: 3, + NumberOfWarn: 0, Score: 10, }, }, diff --git a/checks/evaluation/branch_protection.go b/checks/evaluation/branch_protection.go index 7703cab920c..1a66347c663 100644 --- a/checks/evaluation/branch_protection.go +++ b/checks/evaluation/branch_protection.go @@ -25,7 +25,7 @@ import ( const ( minReviews = 2 // Points incremented at each level. - adminNonAdminBasicLevel = 3 // Level 1. + basicLevel = 3 // Level 1. adminNonAdminReviewLevel = 3 // Level 2. nonAdminContextLevel = 2 // Level 3. nonAdminThoroughReviewLevel = 1 // Level 4. @@ -35,7 +35,6 @@ const ( type scoresInfo struct { basic int - adminBasic int review int adminReview int context int @@ -71,7 +70,6 @@ func BranchProtection(name string, dl checker.DetailLogger, }) } score.scores.basic, score.maxes.basic = basicNonAdminProtection(&b, dl) - score.scores.adminBasic, score.maxes.adminBasic = basicAdminProtection(&b, dl) score.scores.review, score.maxes.review = nonAdminReviewProtection(&b) score.scores.adminReview, score.maxes.adminReview = adminReviewProtection(&b, dl) score.scores.context, score.maxes.context = nonAdminContextProtection(&b, dl) @@ -114,15 +112,6 @@ func computeNonAdminBasicScore(scores []levelScore) int { return score } -func computeAdminBasicScore(scores []levelScore) int { - score := 0 - for i := range scores { - s := scores[i] - score += s.scores.adminBasic - } - return score -} - func computeNonAdminReviewScore(scores []levelScore) int { score := 0 for i := range scores { @@ -194,12 +183,9 @@ func computeScore(scores []levelScore) (int, error) { // First, check if they all pass the basic (admin and non-admin) checks. maxBasicScore := maxScore.basic * len(scores) - maxAdminBasicScore := maxScore.adminBasic * len(scores) basicScore := computeNonAdminBasicScore(scores) - adminBasicScore := computeAdminBasicScore(scores) - score += noarmalizeScore(basicScore+adminBasicScore, maxBasicScore+maxAdminBasicScore, adminNonAdminBasicLevel) - if basicScore != maxBasicScore || - adminBasicScore != maxAdminBasicScore { + score += noarmalizeScore(basicScore, maxBasicScore, basicLevel) + if basicScore != maxBasicScore { return int(score), nil } @@ -308,30 +294,6 @@ func basicNonAdminProtection(branch *clients.BranchRef, dl checker.DetailLogger) return score, max } -func basicAdminProtection(branch *clients.BranchRef, dl checker.DetailLogger) (int, int) { - score := 0 - max := 0 - // Only log information if the branch is protected. - log := branch.Protected != nil && *branch.Protected - - // nil typically means we do not have access to the value. - if branch.BranchProtectionRule.EnforceAdmins != nil { - // Note: we don't inrecase max possible score for non-admin viewers. - max++ - switch *branch.BranchProtectionRule.EnforceAdmins { - case true: - info(dl, log, "settings apply to administrators on branch '%s'", *branch.Name) - score++ - case false: - warn(dl, log, "settings do not apply to administrators on branch '%s'", *branch.Name) - } - } else { - debug(dl, log, "unable to retrieve whether or not settings apply to administrators on branch '%s'", *branch.Name) - } - - return score, max -} - func nonAdminContextProtection(branch *clients.BranchRef, dl checker.DetailLogger) (int, int) { score := 0 max := 0 @@ -422,6 +384,22 @@ func adminThoroughReviewProtection(branch *clients.BranchRef, dl checker.DetailL } else { debug(dl, log, "unable to retrieve review dismissal on branch '%s'", *branch.Name) } + + // nil typically means we do not have access to the value. + if branch.BranchProtectionRule.EnforceAdmins != nil { + // Note: we don't inrecase max possible score for non-admin viewers. + max++ + switch *branch.BranchProtectionRule.EnforceAdmins { + case true: + info(dl, log, "settings apply to administrators on branch '%s'", *branch.Name) + score++ + case false: + warn(dl, log, "settings do not apply to administrators on branch '%s'", *branch.Name) + } + } else { + debug(dl, log, "unable to retrieve whether or not settings apply to administrators on branch '%s'", *branch.Name) + } + return score, max } diff --git a/checks/evaluation/branch_protection_test.go b/checks/evaluation/branch_protection_test.go index 5fd5e541edb..2b45d116714 100644 --- a/checks/evaluation/branch_protection_test.go +++ b/checks/evaluation/branch_protection_test.go @@ -25,7 +25,6 @@ import ( func testScore(branch *clients.BranchRef, codeownersFiles []string, dl checker.DetailLogger) (int, error) { var score levelScore score.scores.basic, score.maxes.basic = basicNonAdminProtection(branch, dl) - score.scores.adminBasic, score.maxes.adminBasic = basicAdminProtection(branch, dl) score.scores.review, score.maxes.review = nonAdminReviewProtection(branch) score.scores.adminReview, score.maxes.adminReview = adminReviewProtection(branch, dl) score.scores.context, score.maxes.context = nonAdminContextProtection(branch, dl) @@ -53,7 +52,7 @@ func TestIsBranchProtected(t *testing.T) { name: "Nothing is enabled", expected: scut.TestReturn{ Error: nil, - Score: 2, + Score: 3, NumberOfWarn: 7, NumberOfInfo: 2, NumberOfDebug: 0, @@ -98,7 +97,7 @@ func TestIsBranchProtected(t *testing.T) { name: "Required status check enabled", expected: scut.TestReturn{ Error: nil, - Score: 2, + Score: 4, NumberOfWarn: 5, NumberOfInfo: 4, NumberOfDebug: 0, @@ -129,7 +128,7 @@ func TestIsBranchProtected(t *testing.T) { name: "Required status check enabled without checking for status string", expected: scut.TestReturn{ Error: nil, - Score: 2, + Score: 4, NumberOfWarn: 6, NumberOfInfo: 3, NumberOfDebug: 0, @@ -160,7 +159,7 @@ func TestIsBranchProtected(t *testing.T) { name: "Required pull request enabled", expected: scut.TestReturn{ Error: nil, - Score: 2, + Score: 4, NumberOfWarn: 6, NumberOfInfo: 3, NumberOfDebug: 0, @@ -222,7 +221,7 @@ func TestIsBranchProtected(t *testing.T) { name: "Required linear history enabled", expected: scut.TestReturn{ Error: nil, - Score: 2, + Score: 3, NumberOfWarn: 6, NumberOfInfo: 3, NumberOfDebug: 0, diff --git a/checks/evaluation/dependency_update_tool.go b/checks/evaluation/dependency_update_tool.go index a60ab7f26d3..6a45afc599e 100644 --- a/checks/evaluation/dependency_update_tool.go +++ b/checks/evaluation/dependency_update_tool.go @@ -24,9 +24,10 @@ import ( "github.com/ossf/scorecard/v4/probes/toolSonatypeLiftInstalled" ) -// DependencyUpdateTool applies the score policy for the Dependency-Update-Tool check. +// DependencyUpdateTool applies the score policy and logs the details +// for the Dependency-Update-Tool check. func DependencyUpdateTool(name string, - findings []finding.Finding, + findings []finding.Finding, dl checker.DetailLogger, ) checker.CheckResult { expectedProbes := []string{ toolDependabotInstalled.Probe, @@ -42,9 +43,13 @@ func DependencyUpdateTool(name string, for i := range findings { f := &findings[i] if f.Outcome == finding.OutcomePositive { + // Log all findings except the negative ones. + checker.LogFindings(nonNegativeFindings(findings), dl) return checker.CreateMaxScoreResult(name, "update tool detected") } } + // Log all findings. + checker.LogFindings(findings, dl) return checker.CreateMinScoreResult(name, "no update tool detected") } diff --git a/checks/evaluation/dependency_update_tool_test.go b/checks/evaluation/dependency_update_tool_test.go index 98be6cee4c9..bbe0a0e3b0c 100644 --- a/checks/evaluation/dependency_update_tool_test.go +++ b/checks/evaluation/dependency_update_tool_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/ossf/scorecard/v4/checker" + sce "github.com/ossf/scorecard/v4/errors" "github.com/ossf/scorecard/v4/finding" scut "github.com/ossf/scorecard/v4/utests" ) @@ -28,9 +29,7 @@ func TestDependencyUpdateTool(t *testing.T) { tests := []struct { name string findings []finding.Finding - err bool - want checker.CheckResult - expected scut.TestReturn + result scut.TestReturn }{ { name: "dependabot", @@ -52,8 +51,9 @@ func TestDependencyUpdateTool(t *testing.T) { Outcome: finding.OutcomeNegative, }, }, - want: checker.CheckResult{ - Score: 10, + result: scut.TestReturn{ + Score: checker.MaxResultScore, + NumberOfInfo: 1, }, }, { @@ -76,8 +76,9 @@ func TestDependencyUpdateTool(t *testing.T) { Outcome: finding.OutcomeNegative, }, }, - want: checker.CheckResult{ - Score: 10, + result: scut.TestReturn{ + Score: checker.MaxResultScore, + NumberOfInfo: 1, }, }, { @@ -100,8 +101,9 @@ func TestDependencyUpdateTool(t *testing.T) { Outcome: finding.OutcomeNegative, }, }, - want: checker.CheckResult{ - Score: 10, + result: scut.TestReturn{ + Score: checker.MaxResultScore, + NumberOfInfo: 1, }, }, { @@ -128,8 +130,9 @@ func TestDependencyUpdateTool(t *testing.T) { Outcome: finding.OutcomeNegative, }, }, - want: checker.CheckResult{ - Score: 10, + result: scut.TestReturn{ + Score: checker.MaxResultScore, + NumberOfInfo: 1, }, }, { @@ -152,8 +155,9 @@ func TestDependencyUpdateTool(t *testing.T) { Outcome: finding.OutcomeNegative, }, }, - want: checker.CheckResult{ - Score: 0, + result: scut.TestReturn{ + Score: checker.MinResultScore, + NumberOfWarn: 4, }, }, { @@ -172,9 +176,9 @@ func TestDependencyUpdateTool(t *testing.T) { Outcome: finding.OutcomeNegative, }, }, - err: true, - want: checker.CheckResult{ - Score: -1, + result: scut.TestReturn{ + Score: checker.InconclusiveResultScore, + Error: sce.ErrScorecardInternal, }, }, { @@ -201,8 +205,9 @@ func TestDependencyUpdateTool(t *testing.T) { Outcome: finding.OutcomeNegative, }, }, - want: checker.CheckResult{ - Score: -1, + result: scut.TestReturn{ + Score: checker.InconclusiveResultScore, + Error: sce.ErrScorecardInternal, }, }, } @@ -211,13 +216,10 @@ func TestDependencyUpdateTool(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - got := DependencyUpdateTool(tt.name, tt.findings) - if tt.want.Score != got.Score { - t.Errorf("DependencyUpdateTool() got Score = %v, want %v for %v", got.Score, tt.want.Score, tt.name) - } - if tt.err && got.Error == nil { - t.Errorf("DependencyUpdateTool() error = %v, want %v for %v", got.Error, tt.want.Error, tt.name) - return + dl := scut.TestDetailLogger{} + got := DependencyUpdateTool(tt.name, tt.findings, &dl) + if !scut.ValidateTestReturn(t, tt.name, &tt.result, &got, &dl) { + t.Errorf("got %v, expected %v", got, tt.result) } }) } diff --git a/checks/evaluation/finding.go b/checks/evaluation/finding.go new file mode 100644 index 00000000000..f11c8fad34c --- /dev/null +++ b/checks/evaluation/finding.go @@ -0,0 +1,31 @@ +// Copyright 2023 OpenSSF Scorecard Authors +// +// 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. + +package evaluation + +import ( + "github.com/ossf/scorecard/v4/finding" +) + +func nonNegativeFindings(findings []finding.Finding) []finding.Finding { + var ff []finding.Finding + for i := range findings { + f := &findings[i] + if f.Outcome == finding.OutcomeNegative { + continue + } + ff = append(ff, *f) + } + return ff +} diff --git a/checks/evaluation/fuzzing.go b/checks/evaluation/fuzzing.go index 8fe621eda85..f058c2e6fe0 100644 --- a/checks/evaluation/fuzzing.go +++ b/checks/evaluation/fuzzing.go @@ -29,7 +29,7 @@ import ( // Fuzzing applies the score policy for the Fuzzing check. func Fuzzing(name string, - findings []finding.Finding, + findings []finding.Finding, dl checker.DetailLogger, ) checker.CheckResult { // We have 7 unique probes, each should have a finding. expectedProbes := []string{ @@ -51,8 +51,12 @@ func Fuzzing(name string, for i := range findings { f := &findings[i] if f.Outcome == finding.OutcomePositive { + // Log all findings except the negative ones. + checker.LogFindings(nonNegativeFindings(findings), dl) return checker.CreateMaxScoreResult(name, "project is fuzzed") } } + // Log all findings. + checker.LogFindings(findings, dl) return checker.CreateMinScoreResult(name, "project is not fuzzed") } diff --git a/checks/evaluation/fuzzing_test.go b/checks/evaluation/fuzzing_test.go index 1300f1e2e01..a1b54847c45 100644 --- a/checks/evaluation/fuzzing_test.go +++ b/checks/evaluation/fuzzing_test.go @@ -16,190 +16,166 @@ package evaluation import ( "testing" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/ossf/scorecard/v4/checker" + sce "github.com/ossf/scorecard/v4/errors" "github.com/ossf/scorecard/v4/finding" + scut "github.com/ossf/scorecard/v4/utests" ) func TestFuzzing(t *testing.T) { t.Parallel() - type args struct { //nolint + tests := []struct { name string findings []finding.Finding - } - tests := []struct { - name string - args args - want checker.CheckResult + result scut.TestReturn }{ { name: "Fuzzing - no fuzzing", - args: args{ - name: "Fuzzing", - findings: []finding.Finding{ - { - Probe: "fuzzedWithClusterFuzzLite", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithGoNative", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithOneFuzz", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithOSSFuzz", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithPropertyBasedHaskell", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithPropertyBasedJavascript", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithPropertyBasedTypescript", - Outcome: finding.OutcomeNegative, - }, + findings: []finding.Finding{ + { + Probe: "fuzzedWithClusterFuzzLite", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithGoNative", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithOneFuzz", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithOSSFuzz", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithPropertyBasedHaskell", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithPropertyBasedJavascript", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithPropertyBasedTypescript", + Outcome: finding.OutcomeNegative, }, }, - want: checker.CheckResult{ - Score: 0, - Name: "Fuzzing", - Version: 2, - Reason: "project is not fuzzed", + result: scut.TestReturn{ + Score: checker.MinResultScore, + NumberOfWarn: 7, }, }, { name: "Fuzzing - fuzzing GoNative", - args: args{ - name: "Fuzzing", - findings: []finding.Finding{ - { - Probe: "fuzzedWithClusterFuzzLite", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithGoNative", - Outcome: finding.OutcomePositive, - }, - { - Probe: "fuzzedWithOneFuzz", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithOSSFuzz", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithPropertyBasedHaskell", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithPropertyBasedJavascript", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithPropertyBasedTypescript", - Outcome: finding.OutcomeNegative, - }, + findings: []finding.Finding{ + { + Probe: "fuzzedWithClusterFuzzLite", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithGoNative", + Outcome: finding.OutcomePositive, + }, + { + Probe: "fuzzedWithOneFuzz", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithOSSFuzz", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithPropertyBasedHaskell", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithPropertyBasedJavascript", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithPropertyBasedTypescript", + Outcome: finding.OutcomeNegative, }, }, - want: checker.CheckResult{ - Score: 10, - Name: "Fuzzing", - Version: 2, - Reason: "project is fuzzed", + result: scut.TestReturn{ + Score: checker.MaxResultScore, + NumberOfInfo: 1, }, }, + { name: "Fuzzing - fuzzing missing GoNative finding", - args: args{ - name: "Fuzzing", - findings: []finding.Finding{ - { - Probe: "fuzzedWithClusterFuzzLite", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithOneFuzz", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithOSSFuzz", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithPropertyBasedHaskell", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithPropertyBasedJavascript", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithPropertyBasedTypescript", - Outcome: finding.OutcomeNegative, - }, + findings: []finding.Finding{ + { + Probe: "fuzzedWithClusterFuzzLite", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithOneFuzz", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithOSSFuzz", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithPropertyBasedHaskell", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithPropertyBasedJavascript", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithPropertyBasedTypescript", + Outcome: finding.OutcomeNegative, }, }, - want: checker.CheckResult{ - Score: -1, - Name: "Fuzzing", - Version: 2, - Reason: "internal error: invalid probe results", + result: scut.TestReturn{ + Score: checker.InconclusiveResultScore, + Error: sce.ErrScorecardInternal, }, }, { name: "Fuzzing - fuzzing invalid probe name", - args: args{ - name: "Fuzzing", - findings: []finding.Finding{ - { - Probe: "fuzzedWithClusterFuzzLite", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithGoNative", - Outcome: finding.OutcomePositive, - }, - { - Probe: "fuzzedWithOneFuzz", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithOSSFuzz", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithPropertyBasedHaskell", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithPropertyBasedJavascript", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithPropertyBasedTypescript", - Outcome: finding.OutcomeNegative, - }, - { - Probe: "fuzzedWithInvalidProbeName", - Outcome: finding.OutcomePositive, - }, + findings: []finding.Finding{ + { + Probe: "fuzzedWithClusterFuzzLite", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithGoNative", + Outcome: finding.OutcomePositive, + }, + { + Probe: "fuzzedWithOneFuzz", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithOSSFuzz", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithPropertyBasedHaskell", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithPropertyBasedJavascript", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithPropertyBasedTypescript", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "fuzzedWithInvalidProbeName", + Outcome: finding.OutcomePositive, }, }, - want: checker.CheckResult{ - Score: -1, - Name: "Fuzzing", - Version: 2, - Reason: "internal error: invalid probe results", + result: scut.TestReturn{ + Score: checker.InconclusiveResultScore, + Error: sce.ErrScorecardInternal, }, }, } @@ -207,8 +183,10 @@ func TestFuzzing(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - if got := Fuzzing(tt.args.name, tt.args.findings); !cmp.Equal(got, tt.want, cmpopts.IgnoreFields(checker.CheckResult{}, "Error")) { //nolint:lll - t.Errorf("Fuzzing() = %v, want %v", got, cmp.Diff(got, tt.want, cmpopts.IgnoreFields(checker.CheckResult{}, "Error"))) //nolint:lll + dl := scut.TestDetailLogger{} + got := Fuzzing(tt.name, tt.findings, &dl) + if !scut.ValidateTestReturn(t, tt.name, &tt.result, &got, &dl) { + t.Errorf("got %v, expected %v", got, tt.result) } }) } diff --git a/checks/evaluation/pinned_dependencies.go b/checks/evaluation/pinned_dependencies.go index c05038fa7ec..c0af9515214 100644 --- a/checks/evaluation/pinned_dependencies.go +++ b/checks/evaluation/pinned_dependencies.go @@ -15,26 +15,19 @@ package evaluation import ( - "errors" "fmt" "github.com/ossf/scorecard/v4/checker" "github.com/ossf/scorecard/v4/checks/fileparser" sce "github.com/ossf/scorecard/v4/errors" - "github.com/ossf/scorecard/v4/finding" "github.com/ossf/scorecard/v4/remediation" "github.com/ossf/scorecard/v4/rule" ) -var errInvalidValue = errors.New("invalid value") - -type pinnedResult int - -const ( - pinnedUndefined pinnedResult = iota - pinned - notPinned -) +type pinnedResult struct { + pinned int + total int +} // Structure to host information about pinned github // or third party dependencies. @@ -43,6 +36,20 @@ type worklowPinningResult struct { gitHubOwned pinnedResult } +// Weights used for proportional score. +// This defines the priority of pinning a dependency over other dependencies. +// The dependencies from all ecosystems are equally prioritized except +// for GitHub Actions. GitHub Actions can be GitHub-owned or from third-party +// development. The GitHub Actions ecosystem has equal priority compared to other +// ecosystems, but, within GitHub Actions, pinning third-party actions has more +// priority than pinning GitHub-owned actions. +// https://github.com/ossf/scorecard/issues/802 +const ( + gitHubOwnedActionWeight int = 2 + thirdPartyActionWeight int = 8 + normalWeight int = gitHubOwnedActionWeight + thirdPartyActionWeight +) + // PinningDependencies applies the score policy for the Pinned-Dependencies check. func PinningDependencies(name string, c *checker.CheckRequest, r *checker.PinningDependenciesData, @@ -70,7 +77,6 @@ func PinningDependencies(name string, c *checker.CheckRequest, }) continue } - if rr.Msg != nil { dl.Debug(&checker.LogMessage{ Path: rr.Location.Path, @@ -80,7 +86,20 @@ func PinningDependencies(name string, c *checker.CheckRequest, Text: *rr.Msg, Snippet: rr.Location.Snippet, }) - } else { + continue + } + if rr.Pinned == nil { + dl.Debug(&checker.LogMessage{ + Path: rr.Location.Path, + Type: rr.Location.Type, + Offset: rr.Location.Offset, + EndOffset: rr.Location.EndOffset, + Text: fmt.Sprintf("%s has empty Pinned field", rr.Type), + Snippet: rr.Location.Snippet, + }) + continue + } + if !*rr.Pinned { dl.Warn(&checker.LogMessage{ Path: rr.Location.Path, Type: rr.Location.Type, @@ -90,67 +109,37 @@ func PinningDependencies(name string, c *checker.CheckRequest, Snippet: rr.Location.Snippet, Remediation: generateRemediation(remediationMetadata, &rr), }) - - // Update the pinning status. - updatePinningResults(&rr, &wp, pr) } + // Update the pinning status. + updatePinningResults(&rr, &wp, pr) } // Generate scores and Info results. - // GitHub actions. - actionScore, err := createReturnForIsGitHubActionsWorkflowPinned(wp, dl) - if err != nil { - return checker.CreateRuntimeErrorResult(name, err) - } - - // Docker files. - dockerFromScore, err := createReturnForIsDockerfilePinned(pr, dl) - if err != nil { - return checker.CreateRuntimeErrorResult(name, err) - } - - // Docker downloads. - dockerDownloadScore, err := createReturnForIsDockerfileFreeOfInsecureDownloads(pr, dl) - if err != nil { - return checker.CreateRuntimeErrorResult(name, err) - } - - // Script downloads. - scriptScore, err := createReturnForIsShellScriptFreeOfInsecureDownloads(pr, dl) - if err != nil { - return checker.CreateRuntimeErrorResult(name, err) - } - - // Pip installs. - pipScore, err := createReturnForIsPipInstallPinned(pr, dl) - if err != nil { - return checker.CreateRuntimeErrorResult(name, err) + var scores []checker.ProportionalScoreWeighted + // Go through all dependency types + // GitHub Actions need to be handled separately since they are not in pr + scores = append(scores, createScoreForGitHubActionsWorkflow(&wp, dl)...) + // Only exisiting dependencies will be found in pr + // We will only score the ecosystem if there are dependencies + // This results in only existing ecosystems being included in the final score + for t := range pr { + logPinnedResult(dl, pr[t], string(t)) + scores = append(scores, checker.ProportionalScoreWeighted{ + Success: pr[t].pinned, + Total: pr[t].total, + Weight: normalWeight, + }) } - // Npm installs. - npmScore, err := createReturnForIsNpmInstallPinned(pr, dl) - if err != nil { - return checker.CreateRuntimeErrorResult(name, err) + if len(scores) == 0 { + return checker.CreateInconclusiveResult(name, "no dependencies found") } - // Go installs. - goScore, err := createReturnForIsGoInstallPinned(pr, dl) + score, err := checker.CreateProportionalScoreWeighted(scores...) if err != nil { return checker.CreateRuntimeErrorResult(name, err) } - // Scores may be inconclusive. - actionScore = maxScore(0, actionScore) - dockerFromScore = maxScore(0, dockerFromScore) - dockerDownloadScore = maxScore(0, dockerDownloadScore) - scriptScore = maxScore(0, scriptScore) - pipScore = maxScore(0, pipScore) - npmScore = maxScore(0, npmScore) - goScore = maxScore(0, goScore) - - score := checker.AggregateScores(actionScore, dockerFromScore, - dockerDownloadScore, scriptScore, pipScore, npmScore, goScore) - if score == checker.MaxResultScore { return checker.CreateMaxScoreResult(name, "all dependencies are pinned") } @@ -177,13 +166,13 @@ func updatePinningResults(rr *checker.Dependency, // Note: `Snippet` contains `action/name@xxx`, so we cna use it to infer // if it's a GitHub-owned action or not. gitHubOwned := fileparser.IsGitHubOwnedAction(rr.Location.Snippet) - addWorkflowPinnedResult(wp, false, gitHubOwned) + addWorkflowPinnedResult(rr, wp, gitHubOwned) return } // Update other result types. - var p pinnedResult - addPinnedResult(&p, false) + p := pr[rr.Type] + addPinnedResult(rr, &p) pr[rr.Type] = p } @@ -192,7 +181,7 @@ func generateText(rr *checker.Dependency) string { // Check if we are dealing with a GitHub action or a third-party one. gitHubOwned := fileparser.IsGitHubOwnedAction(rr.Location.Snippet) owner := generateOwnerToDisplay(gitHubOwned) - return fmt.Sprintf("%s %s not pinned by hash", owner, rr.Type) + return fmt.Sprintf("%s not pinned by hash", owner) } return fmt.Sprintf("%s not pinned by hash", rr.Type) @@ -200,149 +189,69 @@ func generateText(rr *checker.Dependency) string { func generateOwnerToDisplay(gitHubOwned bool) string { if gitHubOwned { - return "GitHub-owned" + return fmt.Sprintf("GitHub-owned %s", checker.DependencyUseTypeGHAction) } - return "third-party" + return fmt.Sprintf("third-party %s", checker.DependencyUseTypeGHAction) } -// TODO(laurent): need to support GCB pinning. -func maxScore(s1, s2 int) int { - if s1 > s2 { - return s1 +func addPinnedResult(rr *checker.Dependency, r *pinnedResult) { + if *rr.Pinned { + r.pinned += 1 } - return s2 + r.total += 1 } -// For the 'to' param, true means the file is pinning dependencies (or there are no dependencies), -// false means there are unpinned dependencies. -func addPinnedResult(r *pinnedResult, to bool) { - // If the result is `notPinned`, we keep it. - // In other cases, we always update the result. - if *r == notPinned { - return - } - - switch to { - case true: - *r = pinned - case false: - *r = notPinned - } -} - -func addWorkflowPinnedResult(w *worklowPinningResult, to, isGitHub bool) { +func addWorkflowPinnedResult(rr *checker.Dependency, w *worklowPinningResult, isGitHub bool) { if isGitHub { - addPinnedResult(&w.gitHubOwned, to) + addPinnedResult(rr, &w.gitHubOwned) } else { - addPinnedResult(&w.thirdParties, to) + addPinnedResult(rr, &w.thirdParties) } } -// Create the result for scripts. -func createReturnForIsShellScriptFreeOfInsecureDownloads(pr map[checker.DependencyUseType]pinnedResult, - dl checker.DetailLogger, -) (int, error) { - return createReturnValues(pr, checker.DependencyUseTypeDownloadThenRun, - "no insecure (not pinned by hash) dependency downloads found in shell scripts", - dl) -} - -// Create the result for docker containers. -func createReturnForIsDockerfilePinned(pr map[checker.DependencyUseType]pinnedResult, - dl checker.DetailLogger, -) (int, error) { - return createReturnValues(pr, checker.DependencyUseTypeDockerfileContainerImage, - "Dockerfile dependencies are pinned", - dl) -} - -// Create the result for docker commands. -func createReturnForIsDockerfileFreeOfInsecureDownloads(pr map[checker.DependencyUseType]pinnedResult, - dl checker.DetailLogger, -) (int, error) { - return createReturnValues(pr, checker.DependencyUseTypeDownloadThenRun, - "no insecure (not pinned by hash) dependency downloads found in Dockerfiles", - dl) -} - -// Create the result for pip install commands. -func createReturnForIsPipInstallPinned(pr map[checker.DependencyUseType]pinnedResult, - dl checker.DetailLogger, -) (int, error) { - return createReturnValues(pr, checker.DependencyUseTypePipCommand, - "Pip installs are pinned", - dl) +func logPinnedResult(dl checker.DetailLogger, p pinnedResult, name string) { + dl.Info(&checker.LogMessage{ + Text: fmt.Sprintf("%3d out of %3d %s dependencies pinned", p.pinned, p.total, name), + }) } -// Create the result for npm install commands. -func createReturnForIsNpmInstallPinned(pr map[checker.DependencyUseType]pinnedResult, - dl checker.DetailLogger, -) (int, error) { - return createReturnValues(pr, checker.DependencyUseTypeNpmCommand, - "npm installs are pinned", - dl) -} - -// Create the result for go install commands. -func createReturnForIsGoInstallPinned(pr map[checker.DependencyUseType]pinnedResult, - dl checker.DetailLogger, -) (int, error) { - return createReturnValues(pr, checker.DependencyUseTypeGoCommand, - "go installs are pinned", - dl) -} - -func createReturnValues(pr map[checker.DependencyUseType]pinnedResult, - t checker.DependencyUseType, infoMsg string, - dl checker.DetailLogger, -) (int, error) { - // Note: we don't check if the entry exists, - // as it will have the default value which is handled in the switch statement. - //nolint - r, _ := pr[t] - switch r { - default: - return checker.InconclusiveResultScore, fmt.Errorf("%w: %v", errInvalidValue, r) - case pinned, pinnedUndefined: - dl.Info(&checker.LogMessage{ - Text: infoMsg, - }) - return checker.MaxResultScore, nil - case notPinned: - // No logging needed as it's done by the checks. - return checker.MinResultScore, nil +func createScoreForGitHubActionsWorkflow(wp *worklowPinningResult, dl checker.DetailLogger, +) []checker.ProportionalScoreWeighted { + if wp.gitHubOwned.total == 0 && wp.thirdParties.total == 0 { + return []checker.ProportionalScoreWeighted{} } -} - -// Create the result. -func createReturnForIsGitHubActionsWorkflowPinned(wp worklowPinningResult, dl checker.DetailLogger) (int, error) { - return createReturnValuesForGitHubActionsWorkflowPinned(wp, - fmt.Sprintf("%ss are pinned", checker.DependencyUseTypeGHAction), - dl) -} - -func createReturnValuesForGitHubActionsWorkflowPinned(r worklowPinningResult, infoMsg string, - dl checker.DetailLogger, -) (int, error) { - score := checker.MinResultScore - - if r.gitHubOwned != notPinned { - score += 2 - dl.Info(&checker.LogMessage{ - Type: finding.FileTypeSource, - Offset: checker.OffsetDefault, - Text: fmt.Sprintf("%s %s", "GitHub-owned", infoMsg), - }) + if wp.gitHubOwned.total != 0 && wp.thirdParties.total != 0 { + logPinnedResult(dl, wp.gitHubOwned, generateOwnerToDisplay(true)) + logPinnedResult(dl, wp.thirdParties, generateOwnerToDisplay(false)) + return []checker.ProportionalScoreWeighted{ + { + Success: wp.gitHubOwned.pinned, + Total: wp.gitHubOwned.total, + Weight: gitHubOwnedActionWeight, + }, + { + Success: wp.thirdParties.pinned, + Total: wp.thirdParties.total, + Weight: thirdPartyActionWeight, + }, + } } - - if r.thirdParties != notPinned { - score += 8 - dl.Info(&checker.LogMessage{ - Type: finding.FileTypeSource, - Offset: checker.OffsetDefault, - Text: fmt.Sprintf("%s %s", "Third-party", infoMsg), - }) + if wp.gitHubOwned.total != 0 { + logPinnedResult(dl, wp.gitHubOwned, generateOwnerToDisplay(true)) + return []checker.ProportionalScoreWeighted{ + { + Success: wp.gitHubOwned.pinned, + Total: wp.gitHubOwned.total, + Weight: normalWeight, + }, + } + } + logPinnedResult(dl, wp.thirdParties, generateOwnerToDisplay(false)) + return []checker.ProportionalScoreWeighted{ + { + Success: wp.thirdParties.pinned, + Total: wp.thirdParties.total, + Weight: normalWeight, + }, } - - return score, nil } diff --git a/checks/evaluation/pinned_dependencies_test.go b/checks/evaluation/pinned_dependencies_test.go index 0a40c0da9a0..3efb32c3901 100644 --- a/checks/evaluation/pinned_dependencies_test.go +++ b/checks/evaluation/pinned_dependencies_test.go @@ -20,67 +20,208 @@ import ( "github.com/google/go-cmp/cmp" "github.com/ossf/scorecard/v4/checker" + sce "github.com/ossf/scorecard/v4/errors" scut "github.com/ossf/scorecard/v4/utests" ) -func Test_createReturnValuesForGitHubActionsWorkflowPinned(t *testing.T) { +func Test_createScoreForGitHubActionsWorkflow(t *testing.T) { t.Parallel() //nolint - type args struct { - r worklowPinningResult - infoMsg string - dl checker.DetailLogger - } - //nolint tests := []struct { - name string - args args - want int + name string + r worklowPinningResult + scores []checker.ProportionalScoreWeighted }{ { - name: "both actions workflow pinned", - args: args{ - r: worklowPinningResult{ - thirdParties: 1, - gitHubOwned: 1, + name: "GitHub-owned and Third-Party actions pinned", + r: worklowPinningResult{ + gitHubOwned: pinnedResult{ + pinned: 1, + total: 1, + }, + thirdParties: pinnedResult{ + pinned: 1, + total: 1, + }, + }, + scores: []checker.ProportionalScoreWeighted{ + { + Success: 1, + Total: 1, + Weight: 2, + }, + { + Success: 1, + Total: 1, + Weight: 8, }, - dl: &scut.TestDetailLogger{}, }, - want: 10, }, { - name: "github actions workflow pinned", - args: args{ - r: worklowPinningResult{ - thirdParties: 2, - gitHubOwned: 2, + name: "only GitHub-owned actions pinned", + r: worklowPinningResult{ + gitHubOwned: pinnedResult{ + pinned: 1, + total: 1, + }, + thirdParties: pinnedResult{ + pinned: 0, + total: 1, + }, + }, + scores: []checker.ProportionalScoreWeighted{ + { + Success: 1, + Total: 1, + Weight: 2, + }, + { + Success: 0, + Total: 1, + Weight: 8, }, - dl: &scut.TestDetailLogger{}, }, - want: 0, }, { - name: "error in github actions workflow pinned", - args: args{ - r: worklowPinningResult{ - thirdParties: 2, - gitHubOwned: 2, + name: "only Third-Party actions pinned", + r: worklowPinningResult{ + gitHubOwned: pinnedResult{ + pinned: 0, + total: 1, + }, + thirdParties: pinnedResult{ + pinned: 1, + total: 1, + }, + }, + scores: []checker.ProportionalScoreWeighted{ + { + Success: 0, + Total: 1, + Weight: 2, + }, + { + Success: 1, + Total: 1, + Weight: 8, + }, + }, + }, + { + name: "no GitHub actions pinned", + r: worklowPinningResult{ + gitHubOwned: pinnedResult{ + pinned: 0, + total: 1, + }, + thirdParties: pinnedResult{ + pinned: 0, + total: 1, + }, + }, + scores: []checker.ProportionalScoreWeighted{ + { + Success: 0, + Total: 1, + Weight: 2, + }, + { + Success: 0, + Total: 1, + Weight: 8, + }, + }, + }, + { + name: "no GitHub-owned actions and Third-party actions unpinned", + r: worklowPinningResult{ + gitHubOwned: pinnedResult{ + pinned: 0, + total: 0, + }, + thirdParties: pinnedResult{ + pinned: 0, + total: 1, + }, + }, + scores: []checker.ProportionalScoreWeighted{ + { + Success: 0, + Total: 1, + Weight: 10, + }, + }, + }, + { + name: "no Third-party actions and GitHub-owned actions unpinned", + r: worklowPinningResult{ + gitHubOwned: pinnedResult{ + pinned: 0, + total: 1, + }, + thirdParties: pinnedResult{ + pinned: 0, + total: 0, + }, + }, + scores: []checker.ProportionalScoreWeighted{ + { + Success: 0, + Total: 1, + Weight: 10, + }, + }, + }, + { + name: "no GitHub-owned actions and Third-party actions pinned", + r: worklowPinningResult{ + gitHubOwned: pinnedResult{ + pinned: 0, + total: 0, + }, + thirdParties: pinnedResult{ + pinned: 1, + total: 1, + }, + }, + scores: []checker.ProportionalScoreWeighted{ + { + Success: 1, + Total: 1, + Weight: 10, + }, + }, + }, + { + name: "no Third-party actions and GitHub-owned actions pinned", + r: worklowPinningResult{ + gitHubOwned: pinnedResult{ + pinned: 1, + total: 1, + }, + thirdParties: pinnedResult{ + pinned: 0, + total: 0, + }, + }, + scores: []checker.ProportionalScoreWeighted{ + { + Success: 1, + Total: 1, + Weight: 10, }, - dl: &scut.TestDetailLogger{}, }, - want: 0, }, } for _, tt := range tests { tt := tt // Re-initializing variable so it is not changed while executing the closure below t.Run(tt.name, func(t *testing.T) { t.Parallel() - got, err := createReturnValuesForGitHubActionsWorkflowPinned(tt.args.r, tt.args.infoMsg, tt.args.dl) - if err != nil { - t.Errorf("error during createReturnValuesForGitHubActionsWorkflowPinned: %v", err) - } - if got != tt.want { - t.Errorf("createReturnValuesForGitHubActionsWorkflowPinned() = %v, want %v", got, tt.want) + dl := scut.TestDetailLogger{} + actual := createScoreForGitHubActionsWorkflow(&tt.r, &dl) + diff := cmp.Diff(tt.scores, actual) + if diff != "" { + t.Errorf("createScoreForGitHubActionsWorkflow (-want,+got) %+v", diff) } }) } @@ -90,6 +231,10 @@ func asPointer(s string) *string { return &s } +func asBoolPointer(b bool) *bool { + return &b +} + func Test_PinningDependencies(t *testing.T) { t.Parallel() @@ -99,140 +244,319 @@ func Test_PinningDependencies(t *testing.T) { expected scut.TestReturn }{ { - name: "download then run pinned debug", + name: "all dependencies pinned", dependencies: []checker.Dependency{ + { + Location: &checker.File{ + Snippet: "actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(true), + }, + { + Location: &checker.File{ + Snippet: "other/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(true), + }, + { + Location: &checker.File{}, + Type: checker.DependencyUseTypeDockerfileContainerImage, + Pinned: asBoolPointer(true), + }, { Location: &checker.File{}, - Msg: asPointer("some message"), Type: checker.DependencyUseTypeDownloadThenRun, + Pinned: asBoolPointer(true), + }, + { + Location: &checker.File{}, + Type: checker.DependencyUseTypeGoCommand, + Pinned: asBoolPointer(true), + }, + { + Location: &checker.File{}, + Type: checker.DependencyUseTypeNpmCommand, + Pinned: asBoolPointer(true), + }, + { + Location: &checker.File{}, + Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(true), }, }, expected: scut.TestReturn{ Error: nil, - Score: checker.MaxResultScore, + Score: 10, NumberOfWarn: 0, - NumberOfInfo: 8, - NumberOfDebug: 1, + NumberOfInfo: 7, + NumberOfDebug: 0, }, }, { - name: "download then run pinned debug and warn", + name: "all dependencies unpinned", dependencies: []checker.Dependency{ + { + Location: &checker.File{ + Snippet: "actions/checkout@v2", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(false), + }, + { + Location: &checker.File{ + Snippet: "other/checkout@v2", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(false), + }, { Location: &checker.File{}, - Msg: asPointer("some message"), - Type: checker.DependencyUseTypeDownloadThenRun, + Type: checker.DependencyUseTypeDockerfileContainerImage, + Pinned: asBoolPointer(false), }, { Location: &checker.File{}, Type: checker.DependencyUseTypeDownloadThenRun, + Pinned: asBoolPointer(false), + }, + { + Location: &checker.File{}, + Type: checker.DependencyUseTypeGoCommand, + Pinned: asBoolPointer(false), + }, + { + Location: &checker.File{}, + Type: checker.DependencyUseTypeNpmCommand, + Pinned: asBoolPointer(false), + }, + { + Location: &checker.File{}, + Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(false), }, }, expected: scut.TestReturn{ Error: nil, - Score: 7, - NumberOfWarn: 1, - NumberOfInfo: 6, - NumberOfDebug: 1, + Score: 0, + NumberOfWarn: 7, + NumberOfInfo: 7, + NumberOfDebug: 0, }, }, { - name: "various warnings", + name: "1 ecosystem pinned and 1 ecosystem unpinned", dependencies: []checker.Dependency{ { Location: &checker.File{}, Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(false), }, { Location: &checker.File{}, - Type: checker.DependencyUseTypeDownloadThenRun, + Type: checker.DependencyUseTypeGoCommand, + Pinned: asBoolPointer(true), }, + }, + expected: scut.TestReturn{ + Error: nil, + Score: 5, + NumberOfWarn: 1, + NumberOfInfo: 2, + NumberOfDebug: 0, + }, + }, + { + name: "1 ecosystem partially pinned", + dependencies: []checker.Dependency{ { Location: &checker.File{}, - Type: checker.DependencyUseTypeDockerfileContainerImage, + Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(false), }, { Location: &checker.File{}, - Msg: asPointer("debug message"), + Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(true), }, }, expected: scut.TestReturn{ Error: nil, - Score: 4, - NumberOfWarn: 3, - NumberOfInfo: 4, - NumberOfDebug: 1, + Score: 5, + NumberOfWarn: 1, + NumberOfInfo: 1, + NumberOfDebug: 0, }, }, { - name: "unpinned pip install", + name: "no dependencies found", + dependencies: []checker.Dependency{}, + expected: scut.TestReturn{ + Error: nil, + Score: -1, + NumberOfWarn: 0, + NumberOfInfo: 0, + NumberOfDebug: 0, + }, + }, + { + name: "pinned dependency shows no warn message", dependencies: []checker.Dependency{ { Location: &checker.File{}, Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(true), }, }, expected: scut.TestReturn{ Error: nil, - Score: 8, + Score: 10, + NumberOfWarn: 0, + NumberOfInfo: 1, + NumberOfDebug: 0, + }, + }, + { + name: "unpinned dependency shows warn message", + dependencies: []checker.Dependency{ + { + Location: &checker.File{}, + Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(false), + }, + }, + expected: scut.TestReturn{ + Error: nil, + Score: 0, NumberOfWarn: 1, - NumberOfInfo: 7, + NumberOfInfo: 1, NumberOfDebug: 0, }, }, { - name: "undefined pip install", + name: "dependency with parsing error does not count for score and shows debug message", dependencies: []checker.Dependency{ { Location: &checker.File{}, + Msg: asPointer("some message"), Type: checker.DependencyUseTypePipCommand, - Msg: asPointer("debug message"), }, }, expected: scut.TestReturn{ Error: nil, - Score: 10, + Score: -1, NumberOfWarn: 0, - NumberOfInfo: 8, + NumberOfInfo: 0, NumberOfDebug: 1, }, }, { - name: "all dependencies pinned", + name: "dependency missing Pinned info does not count for score and shows debug message", + dependencies: []checker.Dependency{ + { + Location: &checker.File{}, + Type: checker.DependencyUseTypePipCommand, + }, + }, expected: scut.TestReturn{ Error: nil, - Score: 10, + Score: -1, + NumberOfWarn: 0, + NumberOfInfo: 0, + NumberOfDebug: 1, + }, + }, + { + name: "dependency missing Location info and no error message throws error", + dependencies: []checker.Dependency{{}}, + expected: scut.TestReturn{ + Error: sce.ErrScorecardInternal, + Score: -1, NumberOfWarn: 0, - NumberOfInfo: 8, + NumberOfInfo: 0, NumberOfDebug: 0, }, }, { - name: "Validate various warnings and info", + name: "dependency missing Location info with error message shows debug message", + dependencies: []checker.Dependency{{ + Msg: asPointer("some message"), + }}, + expected: scut.TestReturn{ + Error: nil, + Score: -1, + NumberOfWarn: 0, + NumberOfInfo: 0, + NumberOfDebug: 1, + }, + }, + { + name: "unpinned choco install", dependencies: []checker.Dependency{ { Location: &checker.File{}, - Type: checker.DependencyUseTypePipCommand, + Type: checker.DependencyUseTypeChocoCommand, + Pinned: asBoolPointer(false), }, + }, + expected: scut.TestReturn{ + Error: nil, + Score: 0, + NumberOfWarn: 1, + NumberOfInfo: 1, + NumberOfDebug: 0, + }, + }, + { + name: "unpinned Dockerfile container image", + dependencies: []checker.Dependency{ { Location: &checker.File{}, - Type: checker.DependencyUseTypeDownloadThenRun, + Type: checker.DependencyUseTypeDockerfileContainerImage, + Pinned: asBoolPointer(false), }, + }, + expected: scut.TestReturn{ + Error: nil, + Score: 0, + NumberOfWarn: 1, + NumberOfInfo: 1, + NumberOfDebug: 0, + }, + }, + { + name: "unpinned download then run", + dependencies: []checker.Dependency{ { Location: &checker.File{}, - Type: checker.DependencyUseTypeDockerfileContainerImage, + Type: checker.DependencyUseTypeDownloadThenRun, + Pinned: asBoolPointer(false), }, + }, + expected: scut.TestReturn{ + Error: nil, + Score: 0, + NumberOfWarn: 1, + NumberOfInfo: 1, + NumberOfDebug: 0, + }, + }, + { + name: "unpinned go install", + dependencies: []checker.Dependency{ { Location: &checker.File{}, - Msg: asPointer("debug message"), + Type: checker.DependencyUseTypeGoCommand, + Pinned: asBoolPointer(false), }, }, expected: scut.TestReturn{ Error: nil, - Score: 4, - NumberOfWarn: 3, - NumberOfInfo: 4, - NumberOfDebug: 1, + Score: 0, + NumberOfWarn: 1, + NumberOfInfo: 1, + NumberOfDebug: 0, }, }, { @@ -241,177 +565,253 @@ func Test_PinningDependencies(t *testing.T) { { Location: &checker.File{}, Type: checker.DependencyUseTypeNpmCommand, + Pinned: asBoolPointer(false), }, }, expected: scut.TestReturn{ Error: nil, - Score: 8, + Score: 0, NumberOfWarn: 1, - NumberOfInfo: 7, + NumberOfInfo: 1, NumberOfDebug: 0, }, }, { - name: "unpinned go install", + name: "unpinned nuget install", dependencies: []checker.Dependency{ { Location: &checker.File{}, - Type: checker.DependencyUseTypeGoCommand, + Type: checker.DependencyUseTypeNugetCommand, + Pinned: asBoolPointer(false), }, }, expected: scut.TestReturn{ Error: nil, - Score: 8, + Score: 0, NumberOfWarn: 1, - NumberOfInfo: 7, + NumberOfInfo: 1, NumberOfDebug: 0, }, }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - dl := scut.TestDetailLogger{} - c := checker.CheckRequest{Dlogger: &dl} - actual := PinningDependencies("checkname", &c, - &checker.PinningDependenciesData{ - Dependencies: tt.dependencies, - }) - - if !scut.ValidateTestReturn(t, tt.name, &tt.expected, &actual, &dl) { - t.Fail() - } - }) - } -} - -func Test_createReturnValues(t *testing.T) { - t.Parallel() - - type args struct { - pr map[checker.DependencyUseType]pinnedResult - dl *scut.TestDetailLogger - t checker.DependencyUseType - } - - tests := []struct { - name string - args args - want int - }{ { - name: "returns 10 if no error and no pinnedResult", - args: args{ - t: checker.DependencyUseTypeDownloadThenRun, - dl: &scut.TestDetailLogger{}, + name: "unpinned pip install", + dependencies: []checker.Dependency{ + { + Location: &checker.File{}, + Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(false), + }, + }, + expected: scut.TestReturn{ + Error: nil, + Score: 0, + NumberOfWarn: 1, + NumberOfInfo: 1, + NumberOfDebug: 0, }, - want: 10, }, { - name: "returns 10 if pinned undefined", - args: args{ - t: checker.DependencyUseTypeDownloadThenRun, - pr: map[checker.DependencyUseType]pinnedResult{ - checker.DependencyUseTypeDownloadThenRun: pinnedUndefined, + name: "2 unpinned dependencies for 1 ecosystem shows 2 warn messages", + dependencies: []checker.Dependency{ + { + Location: &checker.File{}, + Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(false), }, - dl: &scut.TestDetailLogger{}, + { + Location: &checker.File{}, + Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(false), + }, + }, + expected: scut.TestReturn{ + Error: nil, + Score: 0, + NumberOfWarn: 2, + NumberOfInfo: 1, + NumberOfDebug: 0, }, - want: 10, }, { - name: "returns 10 if pinned", - args: args{ - t: checker.DependencyUseTypeDownloadThenRun, - pr: map[checker.DependencyUseType]pinnedResult{ - checker.DependencyUseTypeDownloadThenRun: pinned, + name: "2 unpinned dependencies for 2 ecosystems shows 2 warn messages", + dependencies: []checker.Dependency{ + { + Location: &checker.File{}, + Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(false), + }, + { + Location: &checker.File{}, + Type: checker.DependencyUseTypeGoCommand, + Pinned: asBoolPointer(false), }, - dl: &scut.TestDetailLogger{}, }, - want: 10, + expected: scut.TestReturn{ + Error: nil, + Score: 0, + NumberOfWarn: 2, + NumberOfInfo: 2, + NumberOfDebug: 0, + }, }, { - name: "returns 0 if unpinned", - args: args{ - t: checker.DependencyUseTypeDownloadThenRun, - pr: map[checker.DependencyUseType]pinnedResult{ - checker.DependencyUseTypeDownloadThenRun: notPinned, + name: "GitHub Actions ecosystem with GitHub-owned pinned", + dependencies: []checker.Dependency{ + { + Location: &checker.File{ + Snippet: "actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(true), }, - dl: &scut.TestDetailLogger{}, }, - want: 0, + expected: scut.TestReturn{ + Error: nil, + Score: 10, + NumberOfWarn: 0, + NumberOfInfo: 1, + NumberOfDebug: 0, + }, }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - got, err := createReturnValues(tt.args.pr, tt.args.t, "some message", tt.args.dl) - if err != nil { - t.Errorf("error during createReturnValues: %v", err) - } - if got != tt.want { - t.Errorf("createReturnValues() = %v, want %v", got, tt.want) - } - - if tt.want < 10 { - return - } - - isExpectedLog := func(logMessage checker.LogMessage, logType checker.DetailType) bool { - return logMessage.Text == "some message" && logType == checker.DetailInfo - } - if !scut.ValidateLogMessage(isExpectedLog, tt.args.dl) { - t.Errorf("test failed: log message not present: %+v", "some message") - } - }) - } -} - -func Test_maxScore(t *testing.T) { - t.Parallel() - type args struct { - s1 int - s2 int - } - tests := []struct { - name string - args args - want int - }{ { - name: "returns s1 if s1 is greater than s2", - args: args{ - s1: 10, - s2: 5, + name: "GitHub Actions ecosystem with third-party pinned", + dependencies: []checker.Dependency{ + { + Location: &checker.File{ + Snippet: "other/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(true), + }, + }, + expected: scut.TestReturn{ + Error: nil, + Score: 10, + NumberOfWarn: 0, + NumberOfInfo: 1, + NumberOfDebug: 0, }, - want: 10, }, { - name: "returns s2 if s2 is greater than s1", - args: args{ - s1: 5, - s2: 10, + name: "GitHub Actions ecosystem with GitHub-owned and third-party pinned", + dependencies: []checker.Dependency{ + { + Location: &checker.File{ + Snippet: "actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(true), + }, + { + Location: &checker.File{ + Snippet: "other/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(true), + }, + }, + expected: scut.TestReturn{ + Error: nil, + Score: 10, + NumberOfWarn: 0, + NumberOfInfo: 2, + NumberOfDebug: 0, }, - want: 10, }, { - name: "returns s1 if s1 is equal to s2", - args: args{ - s1: 10, - s2: 10, + name: "GitHub Actions ecosystem with GitHub-owned and third-party unpinned", + dependencies: []checker.Dependency{ + { + Location: &checker.File{ + Snippet: "actions/checkout@v2", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(false), + }, + { + Location: &checker.File{ + Snippet: "other/checkout@v2", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(false), + }, + }, + expected: scut.TestReturn{ + Error: nil, + Score: 0, + NumberOfWarn: 2, + NumberOfInfo: 2, + NumberOfDebug: 0, + }, + }, + { + name: "GitHub Actions ecosystem with GitHub-owned pinned and third-party unpinned", + dependencies: []checker.Dependency{ + { + Location: &checker.File{ + Snippet: "actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(true), + }, + { + Location: &checker.File{ + Snippet: "other/checkout@v2", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(false), + }, + }, + expected: scut.TestReturn{ + Error: nil, + Score: 2, + NumberOfWarn: 1, + NumberOfInfo: 2, + NumberOfDebug: 0, + }, + }, + { + name: "GitHub Actions ecosystem with GitHub-owned unpinned and third-party pinned", + dependencies: []checker.Dependency{ + { + Location: &checker.File{ + Snippet: "actions/checkout@v2", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(false), + }, + { + Location: &checker.File{ + Snippet: "other/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675", + }, + Type: checker.DependencyUseTypeGHAction, + Pinned: asBoolPointer(true), + }, + }, + expected: scut.TestReturn{ + Error: nil, + Score: 8, + NumberOfWarn: 1, + NumberOfInfo: 2, + NumberOfDebug: 0, }, - want: 10, }, } + for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - if got := maxScore(tt.args.s1, tt.args.s2); got != tt.want { - t.Errorf("maxScore() = %v, want %v", got, tt.want) + + dl := scut.TestDetailLogger{} + c := checker.CheckRequest{Dlogger: &dl} + actual := PinningDependencies("checkname", &c, + &checker.PinningDependenciesData{ + Dependencies: tt.dependencies, + }) + + if !scut.ValidateTestReturn(t, tt.name, &tt.expected, &actual, &dl) { + t.Fail() } }) } @@ -427,12 +827,12 @@ func Test_generateOwnerToDisplay(t *testing.T) { { name: "returns GitHub if gitHubOwned is true", gitHubOwned: true, - want: "GitHub-owned", + want: "GitHub-owned GitHubAction", }, { name: "returns GitHub if gitHubOwned is false", gitHubOwned: false, - want: "third-party", + want: "third-party GitHubAction", }, } for _, tt := range tests { @@ -449,50 +849,112 @@ func Test_generateOwnerToDisplay(t *testing.T) { func Test_addWorkflowPinnedResult(t *testing.T) { t.Parallel() type args struct { - w *worklowPinningResult - to bool - isGitHub bool + dependency *checker.Dependency + w *worklowPinningResult + isGitHub bool } tests := []struct { //nolint:govet name string - want pinnedResult + want *worklowPinningResult args args }{ { - name: "sets pinned to true if to is true", + name: "add pinned GitHub-owned action dependency", args: args{ + dependency: &checker.Dependency{ + Pinned: asBoolPointer(true), + }, w: &worklowPinningResult{}, - to: true, isGitHub: true, }, - want: pinned, + want: &worklowPinningResult{ + thirdParties: pinnedResult{ + pinned: 0, + total: 0, + }, + gitHubOwned: pinnedResult{ + pinned: 1, + total: 1, + }, + }, }, { - name: "sets pinned to false if to is false", + name: "add unpinned GitHub-owned action dependency", args: args{ + dependency: &checker.Dependency{ + Pinned: asBoolPointer(false), + }, w: &worklowPinningResult{}, - to: false, isGitHub: true, }, - want: notPinned, + want: &worklowPinningResult{ + thirdParties: pinnedResult{ + pinned: 0, + total: 0, + }, + gitHubOwned: pinnedResult{ + pinned: 0, + total: 1, + }, + }, + }, + { + name: "add pinned Third-Party action dependency", + args: args{ + dependency: &checker.Dependency{ + Pinned: asBoolPointer(true), + }, + w: &worklowPinningResult{}, + isGitHub: false, + }, + want: &worklowPinningResult{ + thirdParties: pinnedResult{ + pinned: 1, + total: 1, + }, + gitHubOwned: pinnedResult{ + pinned: 0, + total: 0, + }, + }, }, { - name: "sets pinned to undefined if to is false and isGitHub is false", + name: "add unpinned Third-Party action dependency", args: args{ + dependency: &checker.Dependency{ + Pinned: asBoolPointer(false), + }, w: &worklowPinningResult{}, - to: false, isGitHub: false, }, - want: pinnedUndefined, + want: &worklowPinningResult{ + thirdParties: pinnedResult{ + pinned: 0, + total: 1, + }, + gitHubOwned: pinnedResult{ + pinned: 0, + total: 0, + }, + }, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - addWorkflowPinnedResult(tt.args.w, tt.args.to, tt.args.isGitHub) - if tt.args.w.gitHubOwned != tt.want { - t.Errorf("addWorkflowPinnedResult() = %v, want %v", tt.args.w.gitHubOwned, tt.want) + addWorkflowPinnedResult(tt.args.dependency, tt.args.w, tt.args.isGitHub) + if tt.want.thirdParties != tt.args.w.thirdParties { + t.Errorf("addWorkflowPinnedResult Third-party GitHub actions mismatch (-want +got):"+ + "\nThird-party pinned: %s\nThird-party total: %s", + cmp.Diff(tt.want.thirdParties.pinned, tt.args.w.thirdParties.pinned), + cmp.Diff(tt.want.thirdParties.total, tt.args.w.thirdParties.total)) + } + if tt.want.gitHubOwned != tt.args.w.gitHubOwned { + t.Errorf("addWorkflowPinnedResult GitHub-owned GitHub actions mismatch (-want +got):"+ + "\nGitHub-owned pinned: %s\nGitHub-owned total: %s", + cmp.Diff(tt.want.gitHubOwned.pinned, tt.args.w.gitHubOwned.pinned), + cmp.Diff(tt.want.gitHubOwned.total, tt.args.w.gitHubOwned.total)) } }) } @@ -538,50 +1000,193 @@ func TestGenerateText(t *testing.T) { func TestUpdatePinningResults(t *testing.T) { t.Parallel() + type args struct { + dependency *checker.Dependency + w *worklowPinningResult + pr map[checker.DependencyUseType]pinnedResult + } + type want struct { + w *worklowPinningResult + pr map[checker.DependencyUseType]pinnedResult + } tests := []struct { //nolint:govet - name string - dependency *checker.Dependency - expectedPinningResult *worklowPinningResult - expectedPinnedResult map[checker.DependencyUseType]pinnedResult + name string + args args + want want }{ { - name: "GitHub Action - GitHub-owned", - dependency: &checker.Dependency{ - Type: checker.DependencyUseTypeGHAction, - Location: &checker.File{ - Snippet: "actions/checkout@v2", + name: "add pinned GitHub-owned action", + args: args{ + dependency: &checker.Dependency{ + Type: checker.DependencyUseTypeGHAction, + Location: &checker.File{ + Snippet: "actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675", + }, + Pinned: asBoolPointer(true), }, + w: &worklowPinningResult{}, + pr: make(map[checker.DependencyUseType]pinnedResult), }, - expectedPinningResult: &worklowPinningResult{ - thirdParties: 0, - gitHubOwned: 2, + want: want{ + w: &worklowPinningResult{ + thirdParties: pinnedResult{ + pinned: 0, + total: 0, + }, + gitHubOwned: pinnedResult{ + pinned: 1, + total: 1, + }, + }, + pr: make(map[checker.DependencyUseType]pinnedResult), }, - expectedPinnedResult: map[checker.DependencyUseType]pinnedResult{}, }, { - name: "Third party owned.", - dependency: &checker.Dependency{ - Type: checker.DependencyUseTypeGHAction, - Location: &checker.File{ - Snippet: "other/checkout@v2", + name: "add unpinned GitHub-owned action", + args: args{ + dependency: &checker.Dependency{ + Type: checker.DependencyUseTypeGHAction, + Location: &checker.File{ + Snippet: "actions/checkout@v2", + }, + Pinned: asBoolPointer(false), }, + w: &worklowPinningResult{}, + pr: make(map[checker.DependencyUseType]pinnedResult), }, - expectedPinningResult: &worklowPinningResult{ - thirdParties: 2, - gitHubOwned: 0, + want: want{ + w: &worklowPinningResult{ + thirdParties: pinnedResult{ + pinned: 0, + total: 0, + }, + gitHubOwned: pinnedResult{ + pinned: 0, + total: 1, + }, + }, + pr: make(map[checker.DependencyUseType]pinnedResult), + }, + }, + { + name: "add pinned Third-party action", + args: args{ + dependency: &checker.Dependency{ + Type: checker.DependencyUseTypeGHAction, + Location: &checker.File{ + Snippet: "other/checkout@ffa6706ff2127a749973072756f83c532e43ed02", + }, + Pinned: asBoolPointer(true), + }, + w: &worklowPinningResult{}, + pr: make(map[checker.DependencyUseType]pinnedResult), + }, + want: want{ + w: &worklowPinningResult{ + thirdParties: pinnedResult{ + pinned: 1, + total: 1, + }, + gitHubOwned: pinnedResult{ + pinned: 0, + total: 0, + }, + }, + pr: make(map[checker.DependencyUseType]pinnedResult), + }, + }, + { + name: "add unpinned Third-party action", + args: args{ + dependency: &checker.Dependency{ + Type: checker.DependencyUseTypeGHAction, + Location: &checker.File{ + Snippet: "other/checkout@v2", + }, + Pinned: asBoolPointer(false), + }, + w: &worklowPinningResult{}, + pr: make(map[checker.DependencyUseType]pinnedResult), + }, + want: want{ + w: &worklowPinningResult{ + thirdParties: pinnedResult{ + pinned: 0, + total: 1, + }, + gitHubOwned: pinnedResult{ + pinned: 0, + total: 0, + }, + }, + pr: make(map[checker.DependencyUseType]pinnedResult), + }, + }, + { + name: "add pinned pip install", + args: args{ + dependency: &checker.Dependency{ + Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(true), + }, + w: &worklowPinningResult{}, + pr: make(map[checker.DependencyUseType]pinnedResult), + }, + want: want{ + w: &worklowPinningResult{}, + pr: map[checker.DependencyUseType]pinnedResult{ + checker.DependencyUseTypePipCommand: { + pinned: 1, + total: 1, + }, + }, + }, + }, + { + name: "add unpinned pip install", + args: args{ + dependency: &checker.Dependency{ + Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(false), + }, + w: &worklowPinningResult{}, + pr: make(map[checker.DependencyUseType]pinnedResult), + }, + want: want{ + w: &worklowPinningResult{}, + pr: map[checker.DependencyUseType]pinnedResult{ + checker.DependencyUseTypePipCommand: { + pinned: 0, + total: 1, + }, + }, }, - expectedPinnedResult: map[checker.DependencyUseType]pinnedResult{}, }, } for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() - wp := &worklowPinningResult{} - pr := make(map[checker.DependencyUseType]pinnedResult) - updatePinningResults(tc.dependency, wp, pr) - if tc.expectedPinningResult.thirdParties != wp.thirdParties && tc.expectedPinningResult.gitHubOwned != wp.gitHubOwned { //nolint:lll - t.Errorf("updatePinningResults mismatch (-want +got):\n%s", cmp.Diff(tc.expectedPinningResult, wp)) + updatePinningResults(tc.args.dependency, tc.args.w, tc.args.pr) + if tc.want.w.thirdParties != tc.args.w.thirdParties { + t.Errorf("updatePinningResults Third-party GitHub actions mismatch (-want +got):"+ + "\nThird-party pinned: %s\nThird-party total: %s", + cmp.Diff(tc.want.w.thirdParties.pinned, tc.args.w.thirdParties.pinned), + cmp.Diff(tc.want.w.thirdParties.total, tc.args.w.thirdParties.total)) + } + if tc.want.w.gitHubOwned != tc.args.w.gitHubOwned { + t.Errorf("updatePinningResults GitHub-owned GitHub actions mismatch (-want +got):"+ + "\nGitHub-owned pinned: %s\nGitHub-owned total: %s", + cmp.Diff(tc.want.w.gitHubOwned.pinned, tc.args.w.gitHubOwned.pinned), + cmp.Diff(tc.want.w.gitHubOwned.total, tc.args.w.gitHubOwned.total)) + } + for dependencyUseType := range tc.want.pr { + if tc.want.pr[dependencyUseType] != tc.args.pr[dependencyUseType] { + t.Errorf("updatePinningResults %s mismatch (-want +got):\npinned: %s\ntotal: %s", + dependencyUseType, + cmp.Diff(tc.want.pr[dependencyUseType].pinned, tc.args.pr[dependencyUseType].pinned), + cmp.Diff(tc.want.pr[dependencyUseType].total, tc.args.pr[dependencyUseType].total)) + } } }) } diff --git a/checks/evaluation/security_policy.go b/checks/evaluation/security_policy.go index 736fdf3ade4..10125c6a312 100644 --- a/checks/evaluation/security_policy.go +++ b/checks/evaluation/security_policy.go @@ -25,7 +25,7 @@ import ( ) // SecurityPolicy applies the score policy for the Security-Policy check. -func SecurityPolicy(name string, findings []finding.Finding) checker.CheckResult { +func SecurityPolicy(name string, findings []finding.Finding, dl checker.DetailLogger) checker.CheckResult { // We have 4 unique probes, each should have a finding. expectedProbes := []string{ securityPolicyContainsVulnerabilityDisclosure.Probe, @@ -64,9 +64,18 @@ func SecurityPolicy(name string, findings []finding.Finding) checker.CheckResult e := sce.WithMessage(sce.ErrScorecardInternal, "score calculation problem") return checker.CreateRuntimeErrorResult(name, e) } + + // Log all findings. + checker.LogFindings(findings, dl) return checker.CreateMinScoreResult(name, "security policy file not detected") } + // Log all findings. + // NOTE: if the score is checker.MaxResultScore, then all findings are positive. + // If the score is less than checker.MaxResultScore, some findings are negative, + // so we log both positive and negative findings. + checker.LogFindings(findings, dl) + return checker.CreateResultWithScore(name, "security policy file detected", score) } diff --git a/checks/evaluation/security_policy_test.go b/checks/evaluation/security_policy_test.go index 35a8563f8bb..2e3f9395d82 100644 --- a/checks/evaluation/security_policy_test.go +++ b/checks/evaluation/security_policy_test.go @@ -18,7 +18,9 @@ import ( "testing" "github.com/ossf/scorecard/v4/checker" + sce "github.com/ossf/scorecard/v4/errors" "github.com/ossf/scorecard/v4/finding" + scut "github.com/ossf/scorecard/v4/utests" ) func TestSecurityPolicy(t *testing.T) { @@ -27,8 +29,7 @@ func TestSecurityPolicy(t *testing.T) { tests := []struct { name string findings []finding.Finding - err bool - want checker.CheckResult + result scut.TestReturn }{ { name: "missing findings links", @@ -46,8 +47,9 @@ func TestSecurityPolicy(t *testing.T) { Outcome: finding.OutcomeNegative, }, }, - want: checker.CheckResult{ - Score: -1, + result: scut.TestReturn{ + Score: checker.InconclusiveResultScore, + Error: sce.ErrScorecardInternal, }, }, { @@ -74,8 +76,9 @@ func TestSecurityPolicy(t *testing.T) { Outcome: finding.OutcomeNegative, }, }, - want: checker.CheckResult{ - Score: -1, + result: scut.TestReturn{ + Score: checker.InconclusiveResultScore, + Error: sce.ErrScorecardInternal, }, }, { @@ -98,8 +101,10 @@ func TestSecurityPolicy(t *testing.T) { Outcome: finding.OutcomePositive, }, }, - want: checker.CheckResult{ - Score: 0, + result: scut.TestReturn{ + Score: checker.MinResultScore, + NumberOfInfo: 1, + NumberOfWarn: 3, }, }, { @@ -122,8 +127,9 @@ func TestSecurityPolicy(t *testing.T) { Outcome: finding.OutcomeNegative, }, }, - want: checker.CheckResult{ - Score: -1, + result: scut.TestReturn{ + Score: checker.InconclusiveResultScore, + Error: sce.ErrScorecardInternal, }, }, { @@ -146,8 +152,10 @@ func TestSecurityPolicy(t *testing.T) { Outcome: finding.OutcomePositive, }, }, - want: checker.CheckResult{ - Score: 6, + result: scut.TestReturn{ + Score: 6, + NumberOfInfo: 2, + NumberOfWarn: 2, }, }, { @@ -170,8 +178,9 @@ func TestSecurityPolicy(t *testing.T) { Outcome: finding.OutcomePositive, }, }, - want: checker.CheckResult{ - Score: 10, + result: scut.TestReturn{ + Score: checker.MaxResultScore, + NumberOfInfo: 4, }, }, } @@ -180,15 +189,10 @@ func TestSecurityPolicy(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - - got := SecurityPolicy("SecurityPolicy", tt.findings) - if tt.err { - if got.Score != -1 { - t.Errorf("SecurityPolicy() = %v, want %v", got, tt.want) - } - } - if got.Score != tt.want.Score { - t.Errorf("SecurityPolicy() = %v, want %v for %v", got.Score, tt.want.Score, tt.name) + dl := scut.TestDetailLogger{} + got := SecurityPolicy(tt.name, tt.findings, &dl) + if !scut.ValidateTestReturn(t, tt.name, &tt.result, &got, &dl) { + t.Errorf("got %v, expected %v", got, tt.result) } }) } diff --git a/checks/fileparser/github_workflow_test.go b/checks/fileparser/github_workflow_test.go index cf2912e4fc2..2c809467ac2 100644 --- a/checks/fileparser/github_workflow_test.go +++ b/checks/fileparser/github_workflow_test.go @@ -20,8 +20,8 @@ import ( "strings" "testing" + "github.com/google/go-cmp/cmp" "github.com/rhysd/actionlint" - "gotest.tools/assert/cmp" ) func TestGitHubWorkflowShell(t *testing.T) { @@ -142,7 +142,7 @@ func TestGitHubWorkflowShell(t *testing.T) { actualShells = append(actualShells, shell) } } - if !cmp.DeepEqual(tt.expectedShells, actualShells)().Success() { + if !cmp.Equal(tt.expectedShells, actualShells) { t.Errorf("%v: Got (%v) expected (%v)", tt.name, actualShells, tt.expectedShells) } }) diff --git a/checks/fuzzing.go b/checks/fuzzing.go index 6d26df57477..6774830101c 100644 --- a/checks/fuzzing.go +++ b/checks/fuzzing.go @@ -20,6 +20,7 @@ import ( "github.com/ossf/scorecard/v4/checks/raw" sce "github.com/ossf/scorecard/v4/errors" "github.com/ossf/scorecard/v4/probes" + "github.com/ossf/scorecard/v4/probes/zrunner" ) // CheckFuzzing is the registered name for Fuzzing. @@ -46,12 +47,12 @@ func Fuzzing(c *checker.CheckRequest) checker.CheckResult { pRawResults.FuzzingResults = rawData // Evaluate the probes. - findings, err := evaluateProbes(c, pRawResults, probes.Fuzzing) + findings, err := zrunner.Run(pRawResults, probes.Fuzzing) if err != nil { e := sce.WithMessage(sce.ErrScorecardInternal, err.Error()) return checker.CreateRuntimeErrorResult(CheckFuzzing, e) } // Return the score evaluation. - return evaluation.Fuzzing(CheckFuzzing, findings) + return evaluation.Fuzzing(CheckFuzzing, findings, c.Dlogger) } diff --git a/checks/fuzzing_test.go b/checks/fuzzing_test.go index 5fd34bbdb9d..191eedfd698 100644 --- a/checks/fuzzing_test.go +++ b/checks/fuzzing_test.go @@ -76,7 +76,7 @@ func TestFuzzing(t *testing.T) { }, wantErr: false, expected: scut.TestReturn{ - NumberOfWarn: 6, + NumberOfWarn: 0, NumberOfDebug: 0, NumberOfInfo: 1, Score: 10, diff --git a/checks/run_probes.go b/checks/probes.go similarity index 59% rename from checks/run_probes.go rename to checks/probes.go index 22ff0e58249..357da984969 100644 --- a/checks/run_probes.go +++ b/checks/probes.go @@ -15,31 +15,9 @@ package checks import ( - "fmt" - "github.com/ossf/scorecard/v4/checker" - "github.com/ossf/scorecard/v4/finding" - "github.com/ossf/scorecard/v4/probes" - "github.com/ossf/scorecard/v4/probes/zrunner" ) -// evaluateProbes runs the probes in probesToRun and logs its findings. -func evaluateProbes(c *checker.CheckRequest, rawResults *checker.RawResults, - probesToRun []probes.ProbeImpl, -) ([]finding.Finding, error) { - // Run the probes. - findings, err := zrunner.Run(rawResults, probesToRun) - if err != nil { - return nil, fmt.Errorf("zrunner.Run: %w", err) - } - - // Log the findings. - if err := checker.LogFindings(findings, c.Dlogger); err != nil { - return nil, fmt.Errorf("LogFindings: %w", err) - } - return findings, nil -} - // getRawResults returns a pointer to the raw results in the CheckRequest // if the pointer is not nil. Else, it creates a new raw result. func getRawResults(c *checker.CheckRequest) *checker.RawResults { diff --git a/checks/raw/dependency_update_tool.go b/checks/raw/dependency_update_tool.go index 3af013b4088..2e2244c1b8b 100644 --- a/checks/raw/dependency_update_tool.go +++ b/checks/raw/dependency_update_tool.go @@ -136,3 +136,7 @@ var checkDependencyFileExists fileparser.DoWhileTrueOnFilename = func(name strin func asPointer(s string) *string { return &s } + +func asBoolPointer(b bool) *bool { + return &b +} diff --git a/checks/raw/pinned_dependencies.go b/checks/raw/pinned_dependencies.go index 0b93181443e..90b28bdff8a 100644 --- a/checks/raw/pinned_dependencies.go +++ b/checks/raw/pinned_dependencies.go @@ -261,7 +261,6 @@ var validateDockerfilesPinning fileparser.DoWhileTrueOnFileContent = func( if pinned || regex.MatchString(name) { // Record the asName. pinnedAsNames[asName] = true - continue } pdata.Dependencies = append(pdata.Dependencies, @@ -275,6 +274,7 @@ var validateDockerfilesPinning fileparser.DoWhileTrueOnFileContent = func( }, Name: asPointer(name), PinnedAt: asPointer(asName), + Pinned: asBoolPointer(pinnedAsNames[asName]), Type: checker.DependencyUseTypeDockerfileContainerImage, }, ) @@ -283,27 +283,26 @@ var validateDockerfilesPinning fileparser.DoWhileTrueOnFileContent = func( case len(valueList) == 1: name := valueList[0] pinned := pinnedAsNames[name] - if !pinned && !regex.MatchString(name) { - dep := checker.Dependency{ - Location: &checker.File{ - Path: pathfn, - Type: finding.FileTypeSource, - Offset: uint(child.StartLine), - EndOffset: uint(child.EndLine), - Snippet: child.Original, - }, - Type: checker.DependencyUseTypeDockerfileContainerImage, - } - parts := strings.SplitN(name, ":", 2) - if len(parts) > 0 { - dep.Name = asPointer(parts[0]) - if len(parts) > 1 { - dep.PinnedAt = asPointer(parts[1]) - } + + dep := checker.Dependency{ + Location: &checker.File{ + Path: pathfn, + Type: finding.FileTypeSource, + Offset: uint(child.StartLine), + EndOffset: uint(child.EndLine), + Snippet: child.Original, + }, + Pinned: asBoolPointer(pinned || regex.MatchString(name)), + Type: checker.DependencyUseTypeDockerfileContainerImage, + } + parts := strings.SplitN(name, ":", 2) + if len(parts) > 0 { + dep.Name = asPointer(parts[0]) + if len(parts) > 1 { + dep.PinnedAt = asPointer(parts[1]) } - pdata.Dependencies = append(pdata.Dependencies, dep) } - + pdata.Dependencies = append(pdata.Dependencies, dep) default: // That should not happen. return false, sce.WithMessage(sce.ErrScorecardInternal, errInternalInvalidDockerFile.Error()) @@ -470,26 +469,25 @@ var validateGitHubActionWorkflow fileparser.DoWhileTrueOnFileContent = func( continue } - if !isActionDependencyPinned(execAction.Uses.Value) { - dep := checker.Dependency{ - Location: &checker.File{ - Path: pathfn, - Type: finding.FileTypeSource, - Offset: uint(execAction.Uses.Pos.Line), - EndOffset: uint(execAction.Uses.Pos.Line), // `Uses` always span a single line. - Snippet: execAction.Uses.Value, - }, - Type: checker.DependencyUseTypeGHAction, - } - parts := strings.SplitN(execAction.Uses.Value, "@", 2) - if len(parts) > 0 { - dep.Name = asPointer(parts[0]) - if len(parts) > 1 { - dep.PinnedAt = asPointer(parts[1]) - } + dep := checker.Dependency{ + Location: &checker.File{ + Path: pathfn, + Type: finding.FileTypeSource, + Offset: uint(execAction.Uses.Pos.Line), + EndOffset: uint(execAction.Uses.Pos.Line), // `Uses` always span a single line. + Snippet: execAction.Uses.Value, + }, + Pinned: asBoolPointer(isActionDependencyPinned(execAction.Uses.Value)), + Type: checker.DependencyUseTypeGHAction, + } + parts := strings.SplitN(execAction.Uses.Value, "@", 2) + if len(parts) > 0 { + dep.Name = asPointer(parts[0]) + if len(parts) > 1 { + dep.PinnedAt = asPointer(parts[1]) } - pdata.Dependencies = append(pdata.Dependencies, dep) } + pdata.Dependencies = append(pdata.Dependencies, dep) } } diff --git a/checks/raw/pinned_dependencies_test.go b/checks/raw/pinned_dependencies_test.go index d1bf323d6c9..bd0e804ff31 100644 --- a/checks/raw/pinned_dependencies_test.go +++ b/checks/raw/pinned_dependencies_test.go @@ -92,8 +92,10 @@ func TestGithubWorkflowPinning(t *testing.T) { return } - if tt.warns != len(r.Dependencies) { - t.Errorf("expected %v. Got %v", tt.warns, len(r.Dependencies)) + unpinned := countUnpinned(r.Dependencies) + + if tt.warns != unpinned { + t.Errorf("expected %v. Got %v", tt.warns, unpinned) } }) } @@ -241,8 +243,10 @@ func TestNonGithubWorkflowPinning(t *testing.T) { return } - if tt.warns != len(r.Dependencies) { - t.Errorf("expected %v. Got %v", tt.warns, len(r.Dependencies)) + unpinned := countUnpinned(r.Dependencies) + + if tt.warns != unpinned { + t.Errorf("expected %v. Got %v", tt.warns, unpinned) } }) } @@ -288,8 +292,10 @@ func TestGithubWorkflowPkgManagerPinning(t *testing.T) { return } - if tt.warns != len(r.Dependencies) { - t.Errorf("expected %v. Got %v", tt.warns, len(r.Dependencies)) + unpinned := countUnpinned(r.Dependencies) + + if tt.warns != unpinned { + t.Errorf("expected %v. Got %v", tt.warns, unpinned) } }) } @@ -365,8 +371,10 @@ func TestDockerfilePinning(t *testing.T) { return } - if tt.warns != len(r.Dependencies) { - t.Errorf("expected %v. Got %v", tt.warns, len(r.Dependencies)) + unpinned := countUnpinned(r.Dependencies) + + if tt.warns != unpinned { + t.Errorf("expected %v. Got %v", tt.warns, unpinned) } }) } @@ -919,8 +927,10 @@ func TestDockerfilePinningWihoutHash(t *testing.T) { return } - if tt.warns != len(r.Dependencies) { - t.Errorf("expected %v. Got %v", tt.warns, len(r.Dependencies)) + unpinned := countUnpinned(r.Dependencies) + + if tt.warns != unpinned { + t.Errorf("expected %v. Got %v", tt.warns, unpinned) } }) } @@ -1022,8 +1032,10 @@ func TestDockerfileScriptDownload(t *testing.T) { return } - if tt.warns != len(r.Dependencies) { - t.Errorf("expected %v. Got %v", tt.warns, len(r.Dependencies)) + unpinned := countUnpinned(r.Dependencies) + + if tt.warns != unpinned { + t.Errorf("expected %v. Got %v", tt.warns, unpinned) } }) } @@ -1064,8 +1076,10 @@ func TestDockerfileScriptDownloadInfo(t *testing.T) { return } - if tt.warns != len(r.Dependencies) { - t.Errorf("expected %v. Got %v", tt.warns, len(r.Dependencies)) + unpinned := countUnpinned(r.Dependencies) + + if tt.warns != unpinned { + t.Errorf("expected %v. Got %v", tt.warns, unpinned) } }) } @@ -1140,12 +1154,14 @@ func TestShellScriptDownload(t *testing.T) { return } + unpinned := countUnpinned(r.Dependencies) + // Note: this works because all our examples // either have warns or debugs. - ws := (tt.warns == len(r.Dependencies)) && (tt.debugs == 0) - ds := (tt.debugs == len(r.Dependencies)) && (tt.warns == 0) + ws := (tt.warns == unpinned) && (tt.debugs == 0) + ds := (tt.debugs == unpinned) && (tt.warns == 0) if !ws && !ds { - t.Errorf("expected %v or %v. Got %v", tt.warns, tt.debugs, len(r.Dependencies)) + t.Errorf("expected %v or %v. Got %v", tt.warns, tt.debugs, unpinned) } }) } @@ -1192,8 +1208,10 @@ func TestShellScriptDownloadPinned(t *testing.T) { return } - if tt.warns != len(r.Dependencies) { - t.Errorf("expected %v. Got %v", tt.warns, len(r.Dependencies)) + unpinned := countUnpinned(r.Dependencies) + + if tt.warns != unpinned { + t.Errorf("expected %v. Got %v", tt.warns, unpinned) } }) } @@ -1251,8 +1269,10 @@ func TestGitHubWorflowRunDownload(t *testing.T) { return } - if tt.warns != len(r.Dependencies) { - t.Errorf("expected %v. Got %v", tt.warns, len(r.Dependencies)) + unpinned := countUnpinned(r.Dependencies) + + if tt.warns != unpinned { + t.Errorf("expected %v. Got %v", tt.warns, unpinned) } }) } @@ -1409,3 +1429,15 @@ func TestGitHubWorkInsecureDownloadsLineNumber(t *testing.T) { }) } } + +func countUnpinned(r []checker.Dependency) int { + var unpinned int + + for _, dependency := range r { + if *dependency.Pinned == false { + unpinned += 1 + } + } + + return unpinned +} diff --git a/checks/raw/shell_download_validate.go b/checks/raw/shell_download_validate.go index 524e6d6e57e..fe7c258c2d5 100644 --- a/checks/raw/shell_download_validate.go +++ b/checks/raw/shell_download_validate.go @@ -337,7 +337,8 @@ func collectFetchPipeExecute(startLine, endLine uint, node syntax.Node, cmd, pat EndOffset: endLine, Snippet: cmd, }, - Type: checker.DependencyUseTypeDownloadThenRun, + Pinned: asBoolPointer(false), + Type: checker.DependencyUseTypeDownloadThenRun, }, ) } @@ -388,7 +389,8 @@ func collectExecuteFiles(startLine, endLine uint, node syntax.Node, cmd, pathfn EndOffset: endLine, Snippet: cmd, }, - Type: checker.DependencyUseTypeDownloadThenRun, + Pinned: asBoolPointer(false), + Type: checker.DependencyUseTypeDownloadThenRun, }, ) } @@ -397,56 +399,50 @@ func collectExecuteFiles(startLine, endLine uint, node syntax.Node, cmd, pathfn // Npm install docs are here. // https://docs.npmjs.com/cli/v7/commands/npm-install -func isNpmUnpinnedDownload(cmd []string) bool { - if len(cmd) == 0 { - return false - } - +func isNpmDownload(cmd []string) bool { if !isBinaryName("npm", cmd[0]) { return false } for i := 1; i < len(cmd); i++ { // Search for get/install/update commands. - // `npm ci` wil verify all hashes are present. if strings.EqualFold(cmd[i], "install") || strings.EqualFold(cmd[i], "i") || strings.EqualFold(cmd[i], "install-test") || - strings.EqualFold(cmd[i], "update") { + strings.EqualFold(cmd[i], "update") || + strings.EqualFold(cmd[i], "ci") { return true } } return false } -func isGoUnpinnedDownload(cmd []string) bool { - if len(cmd) == 0 { - return false +func isNpmUnpinnedDownload(cmd []string) bool { + for i := 1; i < len(cmd); i++ { + // `npm ci` wil verify all hashes are present. + if strings.EqualFold(cmd[i], "ci") { + return false + } } + return true +} - if !isBinaryName("go", cmd[0]) { - return false - } +func isGoDownload(cmd []string) bool { // `Go install` will automatically look up the // go.mod and go.sum, so we don't flag it. if len(cmd) <= 2 { return false } - found := false + return isBinaryName("go", cmd[0]) && slices.Contains([]string{"get", "install"}, cmd[1]) +} + +func isGoUnpinnedDownload(cmd []string) bool { insecure := false hashRegex := regexp.MustCompile("^[A-Fa-f0-9]{40,}$") semverRegex := regexp.MustCompile(`^v\d+\.\d+\.\d+(-[0-9A-Za-z-.]+)?(\+[0-9A-Za-z-.]+)?$`) - for i := 1; i < len(cmd)-1; i++ { - // Search for get and install commands. - if slices.Contains([]string{"get", "install"}, cmd[i]) { - found = true - } - - if !found { - continue - } + for i := 1; i < len(cmd)-1; i++ { // Skip all flags // TODO skip other build flags which might take arguments for i < len(cmd)-1 && slices.Contains([]string{"-d", "-f", "-t", "-u", "-v", "-fix", "-insecure"}, cmd[i+1]) { @@ -485,7 +481,15 @@ func isGoUnpinnedDownload(cmd []string) bool { } } - return found + return true +} + +func isPipInstall(cmd []string) bool { + if len(cmd) < 2 { + return false + } + + return (isBinaryName("pip", cmd[0]) || isBinaryName("pip3", cmd[0])) && strings.EqualFold(cmd[1], "install") } func isPinnedEditableSource(pkgSource string) bool { @@ -509,28 +513,13 @@ func isFlag(cmd string) bool { } func isUnpinnedPipInstall(cmd []string) bool { - if !isBinaryName("pip", cmd[0]) && !isBinaryName("pip3", cmd[0]) { - return false - } - - isInstall := false hasNoDeps := false isEditableInstall := false isPinnedEditableInstall := true hasRequireHashes := false hasAdditionalArgs := false hasWheel := false - for i := 1; i < len(cmd); i++ { - // Search for install commands. - if strings.EqualFold(cmd[i], "install") { - isInstall = true - continue - } - - if !isInstall { - break - } - + for i := 2; i < len(cmd); i++ { // Require --no-deps to not install the dependencies when doing editable install // because we can't verify if dependencies are pinned // https://pip.pypa.io/en/stable/topics/secure-installs/#do-not-use-setuptools-directly @@ -609,7 +598,7 @@ func isUnpinnedPipInstall(cmd []string) bool { // Any other form of install is unpinned, // e.g. `pip install`. - return isInstall + return true } func isPythonCommand(cmd []string) bool { @@ -637,49 +626,52 @@ func extractPipCommand(cmd []string) ([]string, bool) { return nil, false } -func isUnpinnedPythonPipInstall(cmd []string) bool { +func isPythonPipInstall(cmd []string) bool { if !isPythonCommand(cmd) { return false } + pipCommand, ok := extractPipCommand(cmd) if !ok { return false } + + return isPipInstall(pipCommand) +} + +func isUnpinnedPythonPipInstall(cmd []string) bool { + pipCommand, _ := extractPipCommand(cmd) return isUnpinnedPipInstall(pipCommand) } -func isPipUnpinnedDownload(cmd []string) bool { - if len(cmd) == 0 { - return false - } +func isPipDownload(cmd []string) bool { + return isPipInstall(cmd) || isPythonPipInstall(cmd) +} - if isUnpinnedPipInstall(cmd) { +func isPipUnpinnedDownload(cmd []string) bool { + if isPipInstall(cmd) && isUnpinnedPipInstall(cmd) { return true } - if isUnpinnedPythonPipInstall(cmd) { + if isPythonPipInstall(cmd) && isUnpinnedPythonPipInstall(cmd) { return true } return false } -func isChocoUnpinnedDownload(cmd []string) bool { +func isChocoDownload(cmd []string) bool { // Install command is in the form 'choco install ...' if len(cmd) < 2 { return false } - if !isBinaryName("choco", cmd[0]) && !isBinaryName("choco.exe", cmd[0]) { - return false - } - - if !strings.EqualFold(cmd[1], "install") { - return false - } + return (isBinaryName("choco", cmd[0]) || isBinaryName("choco.exe", cmd[0])) && strings.EqualFold(cmd[1], "install") +} +func isChocoUnpinnedDownload(cmd []string) bool { // If this is an install command, then some variant of requirechecksum must be present. - for i := 1; i < len(cmd); i++ { + for i := 2; i < len(cmd); i++ { parts := strings.Split(cmd[i], "=") if len(parts) == 0 { continue @@ -697,22 +689,17 @@ func isChocoUnpinnedDownload(cmd []string) bool { return true } -func isUnpinnedNugetCliInstall(cmd []string) bool { +func isNugetCliInstall(cmd []string) bool { // looking for command of type nuget install ... if len(cmd) < 2 { return false } - // Search for nuget commands. - if !isBinaryName("nuget", cmd[0]) && !isBinaryName("nuget.exe", cmd[0]) { - return false - } - - // Search for install commands. - if !strings.EqualFold(cmd[1], "install") { - return false - } + // Search for nuget install commands. + return (isBinaryName("nuget", cmd[0]) || isBinaryName("nuget.exe", cmd[0])) && strings.EqualFold(cmd[1], "install") +} +func isUnpinnedNugetCliInstall(cmd []string) bool { // Assume installing a project with PackageReference (with versions) // or packages.config at the root of command if len(cmd) == 2 { @@ -740,26 +727,19 @@ func isUnpinnedNugetCliInstall(cmd []string) bool { return unpinnedDependency } -func isUnpinnedDotNetCliInstall(cmd []string) bool { +func isDotNetCliInstall(cmd []string) bool { // Search for command of type dotnet add package if len(cmd) < 4 { return false } - // Search for dotnet commands. - if !isBinaryName("dotnet", cmd[0]) && !isBinaryName("dotnet.exe", cmd[0]) { - return false - } - - // Search for add commands. - if !strings.EqualFold(cmd[1], "add") { - return false - } - - // Search for package commands (can be either the second or the third word) - if !(strings.EqualFold(cmd[2], "package") || strings.EqualFold(cmd[3], "package")) { - return false - } + // Search for dotnet add package + // where package command can be either the second or the third word + return (isBinaryName("dotnet", cmd[0]) || isBinaryName("dotnet.exe", cmd[0])) && + strings.EqualFold(cmd[1], "add") && + (strings.EqualFold(cmd[2], "package") || strings.EqualFold(cmd[3], "package")) +} +func isUnpinnedDotNetCliInstall(cmd []string) bool { unpinnedDependency := true for i := 3; i < len(cmd); i++ { // look for version flag @@ -772,12 +752,16 @@ func isUnpinnedDotNetCliInstall(cmd []string) bool { return unpinnedDependency } +func isNugetDownload(cmd []string) bool { + return isDotNetCliInstall(cmd) || isNugetCliInstall(cmd) +} + func isNugetUnpinnedDownload(cmd []string) bool { - if isUnpinnedDotNetCliInstall(cmd) { + if isDotNetCliInstall(cmd) && isUnpinnedDotNetCliInstall(cmd) { return true } - if isUnpinnedNugetCliInstall(cmd) { + if isNugetCliInstall(cmd) && isUnpinnedNugetCliInstall(cmd) { return true } @@ -799,8 +783,12 @@ func collectUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.N startLine, endLine = getLine(startLine, endLine, node) + if len(c) == 0 { + return + } + // Go get/install. - if isGoUnpinnedDownload(c) { + if isGoDownload(c) { r.Dependencies = append(r.Dependencies, checker.Dependency{ Location: &checker.File{ @@ -810,7 +798,8 @@ func collectUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.N EndOffset: endLine, Snippet: cmd, }, - Type: checker.DependencyUseTypeGoCommand, + Pinned: asBoolPointer(!isGoUnpinnedDownload(c)), + Type: checker.DependencyUseTypeGoCommand, }, ) @@ -818,7 +807,7 @@ func collectUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.N } // Pip install. - if isPipUnpinnedDownload(c) { + if isPipDownload(c) { r.Dependencies = append(r.Dependencies, checker.Dependency{ Location: &checker.File{ @@ -828,7 +817,8 @@ func collectUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.N EndOffset: endLine, Snippet: cmd, }, - Type: checker.DependencyUseTypePipCommand, + Pinned: asBoolPointer(!isPipUnpinnedDownload(c)), + Type: checker.DependencyUseTypePipCommand, }, ) @@ -836,7 +826,7 @@ func collectUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.N } // Npm install. - if isNpmUnpinnedDownload(c) { + if isNpmDownload(c) { r.Dependencies = append(r.Dependencies, checker.Dependency{ Location: &checker.File{ @@ -846,7 +836,8 @@ func collectUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.N EndOffset: endLine, Snippet: cmd, }, - Type: checker.DependencyUseTypeNpmCommand, + Pinned: asBoolPointer(!isNpmUnpinnedDownload(c)), + Type: checker.DependencyUseTypeNpmCommand, }, ) @@ -854,7 +845,7 @@ func collectUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.N } // Choco install. - if isChocoUnpinnedDownload(c) { + if isChocoDownload(c) { r.Dependencies = append(r.Dependencies, checker.Dependency{ Location: &checker.File{ @@ -864,7 +855,8 @@ func collectUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.N EndOffset: endLine, Snippet: cmd, }, - Type: checker.DependencyUseTypeChocoCommand, + Pinned: asBoolPointer(!isChocoUnpinnedDownload(c)), + Type: checker.DependencyUseTypeChocoCommand, }, ) @@ -872,7 +864,7 @@ func collectUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.N } // Nuget install. - if isNugetUnpinnedDownload(c) { + if isNugetDownload(c) { r.Dependencies = append(r.Dependencies, checker.Dependency{ Location: &checker.File{ @@ -882,7 +874,8 @@ func collectUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.N EndOffset: endLine, Snippet: cmd, }, - Type: checker.DependencyUseTypeNugetCommand, + Pinned: asBoolPointer(!isNugetUnpinnedDownload(c)), + Type: checker.DependencyUseTypeNugetCommand, }, ) @@ -977,7 +970,8 @@ func collectFetchProcSubsExecute(startLine, endLine uint, node syntax.Node, cmd, EndOffset: endLine, Snippet: cmd, }, - Type: checker.DependencyUseTypeDownloadThenRun, + Pinned: asBoolPointer(false), + Type: checker.DependencyUseTypeDownloadThenRun, }, ) } diff --git a/checks/raw/shell_download_validate_test.go b/checks/raw/shell_download_validate_test.go index 4624353e29c..efc22927367 100644 --- a/checks/raw/shell_download_validate_test.go +++ b/checks/raw/shell_download_validate_test.go @@ -242,3 +242,69 @@ func Test_isGoUnpinnedDownload(t *testing.T) { }) } } + +func Test_isNpmDownload(t *testing.T) { + type args struct { + cmd []string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "npm install", + args: args{ + cmd: []string{"npm", "install"}, + }, + want: true, + }, + { + name: "npm ci", + args: args{ + cmd: []string{"npm", "ci"}, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isNpmDownload(tt.args.cmd); got != tt.want { + t.Errorf("isNpmDownload() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_isNpmUnpinnedDownload(t *testing.T) { + type args struct { + cmd []string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "npm install", + args: args{ + cmd: []string{"npm", "install"}, + }, + want: true, + }, + { + name: "npm ci", + args: args{ + cmd: []string{"npm", "ci"}, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isNpmUnpinnedDownload(tt.args.cmd); got != tt.want { + t.Errorf("isNpmUnpinnedDownload() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/checks/security_policy.go b/checks/security_policy.go index 902c70327a3..1046c566e13 100644 --- a/checks/security_policy.go +++ b/checks/security_policy.go @@ -20,6 +20,7 @@ import ( "github.com/ossf/scorecard/v4/checks/raw" sce "github.com/ossf/scorecard/v4/errors" "github.com/ossf/scorecard/v4/probes" + "github.com/ossf/scorecard/v4/probes/zrunner" ) // CheckSecurityPolicy is the registred name for SecurityPolicy. @@ -49,12 +50,12 @@ func SecurityPolicy(c *checker.CheckRequest) checker.CheckResult { pRawResults.SecurityPolicyResults = rawData // Evaluate the probes. - findings, err := evaluateProbes(c, pRawResults, probes.SecurityPolicy) + findings, err := zrunner.Run(pRawResults, probes.SecurityPolicy) if err != nil { e := sce.WithMessage(sce.ErrScorecardInternal, err.Error()) return checker.CreateRuntimeErrorResult(CheckSecurityPolicy, e) } // Return the score evaluation. - return evaluation.SecurityPolicy(CheckSecurityPolicy, findings) + return evaluation.SecurityPolicy(CheckSecurityPolicy, findings, c.Dlogger) } diff --git a/clients/cii_client.go b/clients/cii_client.go index c0f407eeaf6..61f5bc969d3 100644 --- a/clients/cii_client.go +++ b/clients/cii_client.go @@ -34,7 +34,7 @@ const ( ) // BadgeLevel corresponds to CII-Best-Practices badge levels. -// https://bestpractices.coreinfrastructure.org/en +// https://www.bestpractices.dev/en type BadgeLevel uint // String returns a string value for BadgeLevel enum. diff --git a/clients/cii_http_client.go b/clients/cii_http_client.go index 3500f63d9d2..4de7532710e 100644 --- a/clients/cii_http_client.go +++ b/clients/cii_http_client.go @@ -49,7 +49,7 @@ func (transport *expBackoffTransport) RoundTrip(req *http.Request) (*http.Respon // GetBadgeLevel implements CIIBestPracticesClient.GetBadgeLevel. func (client *httpClientCIIBestPractices) GetBadgeLevel(ctx context.Context, uri string) (BadgeLevel, error) { repoURI := fmt.Sprintf("https://%s", uri) - url := fmt.Sprintf("https://bestpractices.coreinfrastructure.org/projects.json?url=%s", repoURI) + url := fmt.Sprintf("https://www.bestpractices.dev/projects.json?url=%s", repoURI) req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return Unknown, fmt.Errorf("error during http.NewRequestWithContext: %w", err) diff --git a/clients/githubrepo/branches.go b/clients/githubrepo/branches.go index 5779d72c110..3ddfac7054d 100644 --- a/clients/githubrepo/branches.go +++ b/clients/githubrepo/branches.go @@ -22,8 +22,10 @@ import ( "github.com/google/go-github/v53/github" "github.com/shurcooL/githubv4" + "golang.org/x/exp/slices" "github.com/ossf/scorecard/v4/clients" + "github.com/ossf/scorecard/v4/clients/githubrepo/internal/fnmatch" sce "github.com/ossf/scorecard/v4/errors" ) @@ -33,23 +35,23 @@ const ( // See https://github.community/t/graphql-api-protected-branch/14380 /* Example of query: - query { +query { repository(owner: "laurentsimon", name: "test3") { branchProtectionRules(first: 100) { - edges{ - node{ - allowsDeletions - allowsForcePushes - dismissesStaleReviews - isAdminEnforced - ... - pattern - matchingRefs(first: 100) { - nodes { - name - } - } - } + edges { + node { + allowsDeletions + allowsForcePushes + dismissesStaleReviews + isAdminEnforced + pattern + matchingRefs(first: 100) { + nodes { + name + } + } + } + } } refs(first: 100, refPrefix: "refs/heads/") { nodes { @@ -57,7 +59,56 @@ const ( refUpdateRule { requiredApprovingReviewCount allowsForcePushes - ... + } + } + } + rulesets(first: 100) { + edges { + node { + name + enforcement + target + conditions { + refName { + exclude + include + } + } + bypassActors(first: 100) { + nodes { + actor { + __typename + ... on App { + name + databaseId + } + } + bypassMode + organizationAdmin + repositoryRoleName + } + } + rules(first: 100) { + nodes { + type + parameters { + ... on PullRequestParameters { + dismissStaleReviewsOnPush + requireCodeOwnerReview + requireLastPushApproval + requiredApprovingReviewCount + requiredReviewThreadResolution + } + ... on RequiredStatusChecksParameters { + requiredStatusChecks { + context + integrationId + } + strictRequiredStatusChecksPolicy + } + } + } + } } } } @@ -107,6 +158,63 @@ type defaultBranchData struct { } } +type pullRequestRuleParameters struct { + DismissStaleReviewsOnPush *bool + RequireCodeOwnerReview *bool + RequireLastPushApproval *bool + RequiredApprovingReviewCount *int32 + RequiredReviewThreadResolution *bool +} +type requiredStatusCheckParameters struct { + StrictRequiredStatusChecksPolicy *bool + RequiredStatusChecks []statusCheck +} +type statusCheck struct { + Context *string + IntegrationID *int64 +} +type repoRule struct { + Type string + Parameters repoRulesParameters +} +type repoRulesParameters struct { + PullRequestParameters pullRequestRuleParameters `graphql:"... on PullRequestParameters"` + StatusCheckParameters requiredStatusCheckParameters `graphql:"... on RequiredStatusChecksParameters"` +} +type ruleSetConditionRefs struct { + Include []string + Exclude []string +} +type ruleSetCondition struct { + RefName ruleSetConditionRefs +} +type ruleSetBypass struct { + BypassMode *string + OrganizationAdmin *bool + RepositoryRoleName *string +} +type repoRuleSet struct { + Name *string + Enforcement *string + Conditions ruleSetCondition + BypassActors struct { + Nodes []*ruleSetBypass + } `graphql:"bypassActors(first: 100)"` + Rules struct { + Nodes []*repoRule + } `graphql:"rules(first: 100)"` +} +type ruleSetData struct { + Repository struct { + DefaultBranchRef struct { + Name *string + } + Rulesets struct { + Nodes []*repoRuleSet + } `graphql:"rulesets(first: 100)"` + } `graphql:"repository(owner: $owner, name: $name)"` +} + type branchData struct { Repository struct { Ref *branch `graphql:"ref(qualifiedName: $branchRefName)"` @@ -114,14 +222,16 @@ type branchData struct { } type branchesHandler struct { - ghClient *github.Client - graphClient *githubv4.Client - data *defaultBranchData - once *sync.Once - ctx context.Context - errSetup error - repourl *repoURL - defaultBranchRef *clients.BranchRef + ghClient *github.Client + graphClient *githubv4.Client + data *defaultBranchData + once *sync.Once + ctx context.Context + errSetup error + repourl *repoURL + defaultBranchRef *clients.BranchRef + defaultBranchName string + ruleSets []*repoRuleSet } func (handler *branchesHandler) init(ctx context.Context, repourl *repoURL) { @@ -130,6 +240,8 @@ func (handler *branchesHandler) init(ctx context.Context, repourl *repoURL) { handler.errSetup = nil handler.once = new(sync.Once) handler.defaultBranchRef = nil + handler.defaultBranchName = "" + handler.ruleSets = nil handler.data = nil } @@ -143,12 +255,31 @@ func (handler *branchesHandler) setup() error { "owner": githubv4.String(handler.repourl.owner), "name": githubv4.String(handler.repourl.repo), } + + // Fetch default branch name and any repository rulesets, which are available with basic read permission. + rulesData := new(ruleSetData) + if err := handler.graphClient.Query(handler.ctx, rulesData, vars); err != nil { + handler.errSetup = sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("githubv4.Query: %v", err)) + return + } + handler.defaultBranchName = getDefaultBranchNameFrom(rulesData) + handler.ruleSets = getActiveRuleSetsFrom(rulesData) + + // Attempt to fetch branch protection rules, which require admin permission. + // Ignore permissions errors if we know the repository is using rulesets, so non-admins can still get a score. handler.data = new(defaultBranchData) - if err := handler.graphClient.Query(handler.ctx, handler.data, vars); err != nil { + if err := handler.graphClient.Query(handler.ctx, handler.data, vars); err != nil && + (!isPermissionsError(err) || len(handler.ruleSets) == 0) { handler.errSetup = sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("githubv4.Query: %v", err)) return } - handler.defaultBranchRef = getBranchRefFrom(handler.data.Repository.DefaultBranchRef) + + rules, err := rulesMatchingBranch(handler.ruleSets, handler.defaultBranchName, true) + if err != nil { + handler.errSetup = sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("rulesMatchingBranch: %v", err)) + return + } + handler.defaultBranchRef = getBranchRefFrom(handler.data.Repository.DefaultBranchRef, rules) }) return handler.errSetup } @@ -157,6 +288,10 @@ func (handler *branchesHandler) query(branchName string) (*clients.BranchRef, er if !strings.EqualFold(handler.repourl.commitSHA, clients.HeadSHA) { return nil, fmt.Errorf("%w: branches only supported for HEAD queries", clients.ErrUnsupportedFeature) } + // Call setup(), so we know if branchName == handler.defaultBranchName. + if err := handler.setup(); err != nil { + return nil, fmt.Errorf("error during branchesHandler.setup: %w", err) + } vars := map[string]interface{}{ "owner": githubv4.String(handler.repourl.owner), "name": githubv4.String(handler.repourl.repo), @@ -166,7 +301,11 @@ func (handler *branchesHandler) query(branchName string) (*clients.BranchRef, er if err := handler.graphClient.Query(handler.ctx, queryData, vars); err != nil { return nil, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("githubv4.Query: %v", err)) } - return getBranchRefFrom(queryData.Repository.Ref), nil + rules, err := rulesMatchingBranch(handler.ruleSets, branchName, branchName == handler.defaultBranchName) + if err != nil { + return nil, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("rulesMatchingBranch: %v", err)) + } + return getBranchRefFrom(queryData.Repository.Ref, rules), nil } func (handler *branchesHandler) getDefaultBranch() (*clients.BranchRef, error) { @@ -224,7 +363,25 @@ func copyNonAdminSettings(src interface{}, dst *clients.BranchProtectionRule) { } } -func getBranchRefFrom(data *branch) *clients.BranchRef { +func getDefaultBranchNameFrom(data *ruleSetData) string { + if data == nil || data.Repository.DefaultBranchRef.Name == nil { + return "" + } + return *data.Repository.DefaultBranchRef.Name +} + +func getActiveRuleSetsFrom(data *ruleSetData) []*repoRuleSet { + ret := make([]*repoRuleSet, 0) + for _, rule := range data.Repository.Rulesets.Nodes { + if rule.Enforcement == nil || *rule.Enforcement != "ACTIVE" { + continue + } + ret = append(ret, rule) + } + return ret +} + +func getBranchRefFrom(data *branch, rules []*repoRuleSet) *clients.BranchRef { if data == nil { return nil } @@ -238,7 +395,8 @@ func getBranchRefFrom(data *branch) *clients.BranchRef { // It says nothing about what protection is enabled at all. branchRef.Protected = new(bool) if data.RefUpdateRule == nil && - data.BranchProtectionRule == nil { + data.BranchProtectionRule == nil && + len(rules) == 0 { *branchRef.Protected = false return branchRef } @@ -266,5 +424,178 @@ func getBranchRefFrom(data *branch) *clients.BranchRef { copyNonAdminSettings(rule, branchRule) } + applyRepoRules(branchRef, rules) + return branchRef } + +func isPermissionsError(err error) bool { + return strings.Contains(err.Error(), "Resource not accessible") +} + +const ( + ruleConditionDefaultBranch = "~DEFAULT_BRANCH" + ruleConditionAllBranches = "~ALL" + ruleDeletion = "DELETION" + ruleForcePush = "NON_FAST_FORWARD" + ruleLinear = "REQUIRED_LINEAR_HISTORY" + rulePullRequest = "PULL_REQUEST" + ruleStatusCheck = "REQUIRED_STATUS_CHECKS" +) + +func rulesMatchingBranch(rules []*repoRuleSet, name string, defaultRef bool) ([]*repoRuleSet, error) { + refName := refPrefix + name + ret := make([]*repoRuleSet, 0) +nextRule: + for _, rule := range rules { + for _, cond := range rule.Conditions.RefName.Exclude { + if match, err := fnmatch.Match(cond, refName); err != nil { + return nil, fmt.Errorf("exclude match error: %w", err) + } else if match { + continue nextRule + } + } + + for _, cond := range rule.Conditions.RefName.Include { + if cond == ruleConditionAllBranches { + ret = append(ret, rule) + break + } + if cond == ruleConditionDefaultBranch && defaultRef { + ret = append(ret, rule) + break + } + + if match, err := fnmatch.Match(cond, refName); err != nil { + return nil, fmt.Errorf("include match error: %w", err) + } else if match { + ret = append(ret, rule) + } + } + } + return ret, nil +} + +func applyRepoRules(branchRef *clients.BranchRef, rules []*repoRuleSet) { + falseVal := false + trueVal := true + for _, r := range rules { + adminEnforced := len(r.BypassActors.Nodes) == 0 + translated := clients.BranchProtectionRule{ + EnforceAdmins: &adminEnforced, + } + + for _, rule := range r.Rules.Nodes { + switch rule.Type { + case ruleDeletion: + translated.AllowDeletions = &falseVal + case ruleForcePush: + translated.AllowForcePushes = &falseVal + case ruleLinear: + translated.RequireLinearHistory = &trueVal + case rulePullRequest: + translatePullRequestRepoRule(&translated, rule) + case ruleStatusCheck: + translateRequiredStatusRepoRule(&translated, rule) + } + } + mergeBranchProtectionRules(&branchRef.BranchProtectionRule, &translated) + } +} + +func translatePullRequestRepoRule(base *clients.BranchProtectionRule, rule *repoRule) { + if readBoolPtr(rule.Parameters.PullRequestParameters.DismissStaleReviewsOnPush) { + base.RequiredPullRequestReviews.DismissStaleReviews = rule.Parameters.PullRequestParameters.DismissStaleReviewsOnPush + } + if readBoolPtr(rule.Parameters.PullRequestParameters.RequireCodeOwnerReview) { + base.RequiredPullRequestReviews.RequireCodeOwnerReviews = rule.Parameters.PullRequestParameters.RequireCodeOwnerReview + } + if readBoolPtr(rule.Parameters.PullRequestParameters.RequireLastPushApproval) { + base.RequireLastPushApproval = rule.Parameters.PullRequestParameters.RequireLastPushApproval + } + if reviewerCount := readIntPtr(rule.Parameters.PullRequestParameters.RequiredApprovingReviewCount); reviewerCount > 0 { + base.RequiredPullRequestReviews.RequiredApprovingReviewCount = &reviewerCount + } +} + +func translateRequiredStatusRepoRule(base *clients.BranchProtectionRule, rule *repoRule) { + statusParams := rule.Parameters.StatusCheckParameters + if len(statusParams.RequiredStatusChecks) == 0 { + return + } + enabled := true + base.CheckRules.RequiresStatusChecks = &enabled + base.CheckRules.UpToDateBeforeMerge = statusParams.StrictRequiredStatusChecksPolicy + for _, chk := range statusParams.RequiredStatusChecks { + if chk.Context == nil { + continue + } + base.CheckRules.Contexts = append(base.CheckRules.Contexts, *chk.Context) + } +} + +func mergeBranchProtectionRules(base, translated *clients.BranchProtectionRule) { + if base.AllowDeletions == nil || translated.AllowDeletions != nil && !*translated.AllowDeletions { + base.AllowDeletions = translated.AllowDeletions + } + if base.AllowForcePushes == nil || translated.AllowForcePushes != nil && !*translated.AllowForcePushes { + base.AllowForcePushes = translated.AllowForcePushes + } + if base.EnforceAdmins == nil || translated.EnforceAdmins != nil && !*translated.EnforceAdmins { + // this is an over simplification to get preliminary support for repo rules merged. + // A more complete approach would process all rules without bypass actors first, + // then process those with bypass actors. If no settings improve (due to rule layering), + // then we can ignore the bypass actors. + // https://github.com/ossf/scorecard/issues/3480 + base.EnforceAdmins = translated.EnforceAdmins + } + if base.RequireLastPushApproval == nil || readBoolPtr(translated.RequireLastPushApproval) { + base.RequireLastPushApproval = translated.RequireLastPushApproval + } + if base.RequireLinearHistory == nil || readBoolPtr(translated.RequireLinearHistory) { + base.RequireLinearHistory = translated.RequireLinearHistory + } + mergePullRequestReviews(&base.RequiredPullRequestReviews, &translated.RequiredPullRequestReviews) + mergeCheckRules(&base.CheckRules, &translated.CheckRules) +} + +func mergeCheckRules(base, translated *clients.StatusChecksRule) { + if base.UpToDateBeforeMerge == nil || readBoolPtr(translated.UpToDateBeforeMerge) { + base.UpToDateBeforeMerge = translated.UpToDateBeforeMerge + } + if base.RequiresStatusChecks == nil || readBoolPtr(translated.RequiresStatusChecks) { + base.RequiresStatusChecks = translated.RequiresStatusChecks + } + for _, context := range translated.Contexts { + // this isn't optimal, but probably not a bottleneck. + if !slices.Contains(base.Contexts, context) { + base.Contexts = append(base.Contexts, context) + } + } +} + +func mergePullRequestReviews(base, translated *clients.PullRequestReviewRule) { + if readIntPtr(translated.RequiredApprovingReviewCount) > readIntPtr(base.RequiredApprovingReviewCount) { + base.RequiredApprovingReviewCount = translated.RequiredApprovingReviewCount + } + if base.DismissStaleReviews == nil || readBoolPtr(translated.DismissStaleReviews) { + base.DismissStaleReviews = translated.DismissStaleReviews + } + if base.RequireCodeOwnerReviews == nil || readBoolPtr(translated.RequireCodeOwnerReviews) { + base.RequireCodeOwnerReviews = translated.RequireCodeOwnerReviews + } +} + +func readBoolPtr(b *bool) bool { + if b == nil { + return false + } + return *b +} + +func readIntPtr(i *int32) int32 { + if i == nil { + return 0 + } + return *i +} diff --git a/clients/githubrepo/branches_test.go b/clients/githubrepo/branches_test.go new file mode 100644 index 00000000000..73a9401e141 --- /dev/null +++ b/clients/githubrepo/branches_test.go @@ -0,0 +1,359 @@ +// Copyright 2023 OpenSSF Scorecard Authors +// +// 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. + +package githubrepo + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/ossf/scorecard/v4/clients" +) + +func Test_rulesMatchingBranch(t *testing.T) { + t.Parallel() + testcases := []struct { + name string + defaultBranchNames map[string]bool + nonDefaultBranchNames map[string]bool + condition ruleSetCondition + }{ + { + name: "including all branches", + condition: ruleSetCondition{ + RefName: ruleSetConditionRefs{ + Include: []string{ruleConditionAllBranches}, + }, + }, + defaultBranchNames: map[string]bool{ + "main": true, + "foo": true, + }, + nonDefaultBranchNames: map[string]bool{ + "main": true, + "foo": true, + }, + }, + { + name: "including default branch", + condition: ruleSetCondition{ + RefName: ruleSetConditionRefs{ + Include: []string{ruleConditionDefaultBranch}, + }, + }, + defaultBranchNames: map[string]bool{ + "main": true, + "foo": true, + }, + nonDefaultBranchNames: map[string]bool{ + "main": false, + "foo": false, + }, + }, + { + name: "including branch by name", + condition: ruleSetCondition{ + RefName: ruleSetConditionRefs{ + Include: []string{"refs/heads/foo"}, + }, + }, + defaultBranchNames: map[string]bool{ + "main": false, + "foo": true, + }, + nonDefaultBranchNames: map[string]bool{ + "main": false, + "foo": true, + }, + }, + { + name: "including branch by fnmatch", + condition: ruleSetCondition{ + RefName: ruleSetConditionRefs{ + Include: []string{"refs/heads/foo/**"}, + }, + }, + defaultBranchNames: map[string]bool{ + "main": false, + "foo": false, + "foo/bar": true, + }, + nonDefaultBranchNames: map[string]bool{ + "main": false, + "foo": false, + "foo/bar": true, + }, + }, + { + name: "include+exclude branch by fnmatch", + condition: ruleSetCondition{ + RefName: ruleSetConditionRefs{ + Include: []string{"refs/heads/foo/**"}, + Exclude: []string{"refs/heads/foo/bar"}, + }, + }, + defaultBranchNames: map[string]bool{ + "foo/bar": false, + "foo/baz": true, + }, + nonDefaultBranchNames: map[string]bool{ + "foo/bar": false, + "foo/baz": true, + }, + }, + } + + active := "ACTIVE" + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + inputRules := []*repoRuleSet{{Enforcement: &active, Conditions: testcase.condition}} + for branchName, expected := range testcase.defaultBranchNames { + branchName := branchName + expected := expected + t.Run(fmt.Sprintf("default branch %s", branchName), func(t *testing.T) { + t.Parallel() + matching, err := rulesMatchingBranch(inputRules, branchName, true) + if err != nil { + t.Fatalf("expected - no error, got: %v", err) + } + if matched := len(matching) == 1; matched != expected { + t.Errorf("expected %v, got %v", expected, matched) + } + }) + } + for branchName, expected := range testcase.nonDefaultBranchNames { + branchName := branchName + expected := expected + t.Run(fmt.Sprintf("non-default branch %s", branchName), func(t *testing.T) { + t.Parallel() + matching, err := rulesMatchingBranch(inputRules, branchName, false) + if err != nil { + t.Fatalf("expected - no error, got: %v", err) + } + if matched := len(matching) == 1; matched != expected { + t.Errorf("expected %v, got %v", expected, matched) + } + }) + } + }) + } +} + +type ruleSetOpt func(*repoRuleSet) + +func ruleSet(opts ...ruleSetOpt) *repoRuleSet { + r := &repoRuleSet{} + for _, opt := range opts { + opt(r) + } + return r +} + +func withRules(rules ...*repoRule) ruleSetOpt { + return func(r *repoRuleSet) { + r.Rules.Nodes = append(r.Rules.Nodes, rules...) + } +} + +func withBypass() ruleSetOpt { + return func(r *repoRuleSet) { + r.BypassActors.Nodes = append(r.BypassActors.Nodes, &ruleSetBypass{}) + } +} + +func Test_applyRepoRules(t *testing.T) { + t.Parallel() + trueVal := true + falseVal := false + twoVal := int32(2) + + testcases := []struct { + base *clients.BranchRef + ruleSet *repoRuleSet + expected *clients.BranchRef + ruleBypass *ruleSetBypass + name string + }{ + { + name: "block deletion no bypass", + base: &clients.BranchRef{}, + ruleSet: ruleSet(withRules(&repoRule{Type: ruleDeletion})), + expected: &clients.BranchRef{ + BranchProtectionRule: clients.BranchProtectionRule{ + AllowDeletions: &falseVal, + EnforceAdmins: &trueVal, + }, + }, + }, + { + name: "block deletion with bypass", + base: &clients.BranchRef{}, + ruleSet: ruleSet(withRules(&repoRule{Type: ruleDeletion}), withBypass()), + expected: &clients.BranchRef{ + BranchProtectionRule: clients.BranchProtectionRule{ + AllowDeletions: &falseVal, + EnforceAdmins: &falseVal, + }, + }, + }, + { + name: "block deletion and force push with bypass when block force push no bypass", + base: &clients.BranchRef{ + BranchProtectionRule: clients.BranchProtectionRule{ + AllowForcePushes: &falseVal, + EnforceAdmins: &trueVal, + }, + }, + ruleSet: ruleSet(withRules(&repoRule{Type: ruleDeletion}, &repoRule{Type: ruleForcePush}), withBypass()), + expected: &clients.BranchRef{ + BranchProtectionRule: clients.BranchProtectionRule{ + AllowDeletions: &falseVal, + AllowForcePushes: &falseVal, + EnforceAdmins: &falseVal, // Downgrade: deletion does not enforce admins + }, + }, + }, + { + name: "block deletion no bypass while force push is blocked with bypass", + base: &clients.BranchRef{ + BranchProtectionRule: clients.BranchProtectionRule{ + AllowForcePushes: &falseVal, + EnforceAdmins: &falseVal, + }, + }, + ruleSet: ruleSet(withRules(&repoRule{Type: ruleDeletion})), + expected: &clients.BranchRef{ + BranchProtectionRule: clients.BranchProtectionRule{ + AllowDeletions: &falseVal, + AllowForcePushes: &falseVal, + EnforceAdmins: &falseVal, // Maintain: deletion enforces but forcepush does not + }, + }, + }, + { + name: "block deletion no bypass while force push is blocked no bypass", + base: &clients.BranchRef{ + BranchProtectionRule: clients.BranchProtectionRule{ + AllowForcePushes: &falseVal, + EnforceAdmins: &trueVal, + }, + }, + ruleSet: ruleSet(withRules(&repoRule{Type: ruleDeletion})), + expected: &clients.BranchRef{ + BranchProtectionRule: clients.BranchProtectionRule{ + AllowDeletions: &falseVal, + AllowForcePushes: &falseVal, + EnforceAdmins: &trueVal, // Maintain: base and rule are equal strictness + }, + }, + }, + { + name: "block force push no bypass", + base: &clients.BranchRef{}, + ruleSet: ruleSet(withRules(&repoRule{Type: ruleForcePush})), + expected: &clients.BranchRef{ + BranchProtectionRule: clients.BranchProtectionRule{ + AllowForcePushes: &falseVal, + EnforceAdmins: &trueVal, + }, + }, + }, + { + name: "require linear history no bypass", + base: &clients.BranchRef{}, + ruleSet: ruleSet(withRules(&repoRule{Type: ruleLinear})), + expected: &clients.BranchRef{ + BranchProtectionRule: clients.BranchProtectionRule{ + RequireLinearHistory: &trueVal, + EnforceAdmins: &trueVal, + }, + }, + }, + { + name: "require pull request no bypass", + base: &clients.BranchRef{}, + ruleSet: ruleSet(withRules(&repoRule{ + Type: rulePullRequest, + Parameters: repoRulesParameters{ + PullRequestParameters: pullRequestRuleParameters{ + DismissStaleReviewsOnPush: &trueVal, + RequireCodeOwnerReview: &trueVal, + RequireLastPushApproval: &trueVal, + RequiredApprovingReviewCount: &twoVal, + RequiredReviewThreadResolution: &trueVal, + }, + }, + })), + expected: &clients.BranchRef{ + BranchProtectionRule: clients.BranchProtectionRule{ + EnforceAdmins: &trueVal, + RequireLastPushApproval: &trueVal, + RequiredPullRequestReviews: clients.PullRequestReviewRule{ + DismissStaleReviews: &trueVal, + RequireCodeOwnerReviews: &trueVal, + RequiredApprovingReviewCount: &twoVal, + }, + }, + }, + }, + { + name: "required status checks no bypass", + base: &clients.BranchRef{}, + ruleSet: ruleSet(withRules(&repoRule{ + Type: ruleStatusCheck, + Parameters: repoRulesParameters{ + StatusCheckParameters: requiredStatusCheckParameters{ + StrictRequiredStatusChecksPolicy: &trueVal, + RequiredStatusChecks: []statusCheck{ + { + Context: stringPtr("foo"), + }, + }, + }, + }, + })), + expected: &clients.BranchRef{ + BranchProtectionRule: clients.BranchProtectionRule{ + EnforceAdmins: &trueVal, + CheckRules: clients.StatusChecksRule{ + UpToDateBeforeMerge: &trueVal, + RequiresStatusChecks: &trueVal, + Contexts: []string{"foo"}, + }, + }, + }, + }, + } + + for _, testcase := range testcases { + testcase := testcase + t.Run(testcase.name, func(t *testing.T) { + t.Parallel() + applyRepoRules(testcase.base, []*repoRuleSet{testcase.ruleSet}) + + if !cmp.Equal(testcase.base, testcase.expected) { + diff := cmp.Diff(testcase.base, testcase.expected) + t.Errorf("test failed: expected - %v, got - %v. \n%s", testcase.expected, testcase.base, diff) + } + }) + } +} + +func stringPtr(s string) *string { + return &s +} diff --git a/clients/githubrepo/internal/fnmatch/fnmatch.go b/clients/githubrepo/internal/fnmatch/fnmatch.go new file mode 100644 index 00000000000..fdd26e4c3be --- /dev/null +++ b/clients/githubrepo/internal/fnmatch/fnmatch.go @@ -0,0 +1,94 @@ +// Copyright 2023 OpenSSF Scorecard Authors +// +// 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. + +package fnmatch + +import ( + "fmt" + "regexp" + "strings" +) + +func Match(pattern, path string) (bool, error) { + r := convertToRegex(pattern) + m, err := regexp.MatchString(r, path) + if err != nil { + return false, fmt.Errorf("converted regex invalid: %w", err) + } + return m, nil +} + +var specialRegexpChars = map[byte]struct{}{ + '.': {}, + '+': {}, + '*': {}, + '?': {}, + '^': {}, + '$': {}, + '(': {}, + ')': {}, + '[': {}, + ']': {}, + '{': {}, + '}': {}, + '|': {}, + '\\': {}, +} + +func convertToRegex(pattern string) string { + var regexPattern strings.Builder + regexPattern.WriteRune('^') + for len(pattern) > 0 { + matchLen := 1 + switch { + case len(pattern) > 2 && pattern[:3] == "**/": + // Matches directories recursively + regexPattern.WriteString("(?:[^/]+/?)+") + matchLen = 3 + case len(pattern) > 1 && pattern[:2] == "**": + // Matches files expansively. + regexPattern.WriteString("[^/]+") + matchLen = 2 + case len(pattern) > 1 && pattern[:1] == "\\": + writePotentialRegexpChar(®exPattern, pattern[1]) + matchLen = 2 + default: + switch pattern[0] { + case '*': + // Equivalent to ".*"" in regexp, but GitHub uses the File::FNM_PATHNAME flag for the File.fnmatch syntax + // the * wildcard does not match directory separators (/). + regexPattern.WriteString("[^/]*") + case '?': + // Matches any one character. Equivalent to ".{1}" in regexp, see FNM_PATHNAME note above. + regexPattern.WriteString("[^/]{1}") + case '[', ']': + // "[" and "]" represent character sets in fnmatch too + regexPattern.WriteByte(pattern[0]) + default: + writePotentialRegexpChar(®exPattern, pattern[0]) + } + } + pattern = pattern[matchLen:] + } + regexPattern.WriteRune('$') + return regexPattern.String() +} + +// Characters with special meaning in regexp may need escaped. +func writePotentialRegexpChar(sb *strings.Builder, b byte) { + if _, ok := specialRegexpChars[b]; ok { + sb.WriteRune('\\') + } + sb.WriteByte(b) +} diff --git a/clients/githubrepo/internal/fnmatch/fnmatch_test.go b/clients/githubrepo/internal/fnmatch/fnmatch_test.go new file mode 100644 index 00000000000..3059568b051 --- /dev/null +++ b/clients/githubrepo/internal/fnmatch/fnmatch_test.go @@ -0,0 +1,66 @@ +// Copyright 2023 OpenSSF Scorecard Authors +// +// 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. + +package fnmatch + +import ( + "testing" +) + +func TestMatch(t *testing.T) { + tests := []struct { + pattern string + path string + want bool + }{ + // Taken from https://ruby-doc.org/core-2.5.1/File.html#method-c-fnmatch, assumes File::FNM_PATHNAME + {"cat", "cat", true}, + {"cat", "category", false}, + {"c?t", "cat", true}, + {"c??t", "cat", false}, + {"c*", "cats", true}, + {"ca[a-z]", "cat", true}, + {"cat", "CAT", false}, + {"?", "/", false}, + {"*", "/", false}, + + {"\\?", "?", true}, + {"\\a", "a", true}, + + {"foo.bar", "foo.bar", true}, + {"foo.bar", "foo:bar", false}, + + {"**.rb", "main.rb", true}, + {"**.rb", "./main.rb", false}, + {"**.rb", "lib/song.rb", false}, + + {"**/foo", "a/b/c/foo", true}, + {"**foo", "a/b/c/foo", false}, + + {"main", "main", true}, + {"releases/**/*", "releases/v2", true}, + {"releases/**/**/*", "releases/v2", true}, + {"releases/**/bar/**/qux", "releases/foo/bar/baz/qux", true}, + {"users/**/*", "users/foo/bar", true}, + } + for _, tt := range tests { + got, err := Match(tt.pattern, tt.path) + if err != nil { + t.Errorf("match: %v", err) + } + if got != tt.want { + t.Errorf("Match(%q, %q) = %t, wanted %t", tt.pattern, tt.path, got, tt.want) + } + } +} diff --git a/clients/gitlabrepo/client.go b/clients/gitlabrepo/client.go index f79e502aa56..601d9dbff75 100644 --- a/clients/gitlabrepo/client.go +++ b/clients/gitlabrepo/client.go @@ -163,7 +163,7 @@ func (client *Client) InitRepo(inputRepo clients.Repo, commitSHA string, commitD } func (client *Client) URI() string { - return fmt.Sprintf("%s/%s/%s", client.repourl.host, client.repourl.owner, client.repourl.projectID) + return fmt.Sprintf("%s/%s/%s", client.repourl.host, client.repourl.owner, client.repourl.project) } func (client *Client) LocalPath() (string, error) { diff --git a/clients/gitlabrepo/licenses.go b/clients/gitlabrepo/licenses.go index 7b76963dde6..7208ff815c7 100644 --- a/clients/gitlabrepo/licenses.go +++ b/clients/gitlabrepo/licenses.go @@ -46,6 +46,11 @@ func (handler *licensesHandler) setup() error { handler.once.Do(func() { l := handler.glProject.License + // No registered license on GitLab repo, use file-based license detection instead + if l == nil { + return + } + ptn, err := regexp.Compile(fmt.Sprintf("%s/-/blob/(?:\\w+)/(.*)", handler.repourl.URI())) if err != nil { handler.errSetup = fmt.Errorf("couldn't parse license url: %w", err) diff --git a/cmd/internal/scdiff/app/compare.go b/cmd/internal/scdiff/app/compare.go index 7947c0f721c..8ff1117ffcc 100644 --- a/cmd/internal/scdiff/app/compare.go +++ b/cmd/internal/scdiff/app/compare.go @@ -36,18 +36,15 @@ func init() { } var ( - errMissingInputFiles = errors.New("must provide at least two files from scdiff generate") - errResultsDiffer = errors.New("results differ") - errNumResults = errors.New("number of results being compared differ") + errResultsDiffer = errors.New("results differ") + errNumResults = errors.New("number of results being compared differ") compareCmd = &cobra.Command{ Use: "compare [flags] FILE1 FILE2", Short: "Compare Scorecard results", Long: `Compare Scorecard results`, + Args: cobra.MinimumNArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return errMissingInputFiles - } f1, err := os.Open(args[0]) if err != nil { return fmt.Errorf("opening %q: %w", args[0], err) @@ -68,7 +65,9 @@ var ( func compareReaders(x, y io.Reader, output io.Writer) error { // results are currently newline delimited xs := bufio.NewScanner(x) + xs.Buffer(nil, maxResultSize) ys := bufio.NewScanner(y) + ys.Buffer(nil, maxResultSize) for { if shouldContinue, err := advanceScanners(xs, ys); err != nil { return err @@ -90,11 +89,11 @@ func compareReaders(x, y io.Reader, output io.Writer) error { } func loadResults(x, y *bufio.Scanner) (pkg.ScorecardResult, pkg.ScorecardResult, error) { - xResult, err := pkg.ExperimentalFromJSON2(strings.NewReader(x.Text())) + xResult, _, err := pkg.ExperimentalFromJSON2(strings.NewReader(x.Text())) if err != nil { return pkg.ScorecardResult{}, pkg.ScorecardResult{}, fmt.Errorf("parsing first result: %w", err) } - yResult, err := pkg.ExperimentalFromJSON2(strings.NewReader(y.Text())) + yResult, _, err := pkg.ExperimentalFromJSON2(strings.NewReader(y.Text())) if err != nil { return pkg.ScorecardResult{}, pkg.ScorecardResult{}, fmt.Errorf("parsing second result: %w", err) } diff --git a/cmd/internal/scdiff/app/stats.go b/cmd/internal/scdiff/app/stats.go new file mode 100644 index 00000000000..d1a09eff6b8 --- /dev/null +++ b/cmd/internal/scdiff/app/stats.go @@ -0,0 +1,127 @@ +// Copyright 2023 OpenSSF Scorecard Authors +// +// 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. + +package app + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "strings" + "text/tabwriter" + + "github.com/spf13/cobra" + "golang.org/x/exp/slices" + + "github.com/ossf/scorecard/v4/checker" + "github.com/ossf/scorecard/v4/pkg" +) + +//nolint:gochecknoinits // common for cobra apps +func init() { + rootCmd.AddCommand(statsCmd) + statsCmd.PersistentFlags().StringVarP(&statsCheck, "check", "c", "", "Analyze breakdown of a single check") +} + +// 1 MiB size limit for individual results. This currently works, +// but bufio.Scanner always has a limit, may need to change approach in the future. +const maxResultSize = 1024 * 1024 + +var ( + statsCheck string + statsCmd = &cobra.Command{ + Use: "stats [flags] FILE", + Short: "Summarize stats for a golden file", + Long: `Summarize stats for a golden file`, + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + f1, err := os.Open(args[0]) + if err != nil { + return fmt.Errorf("opening %q: %w", args[0], err) + } + defer f1.Close() + return calcStats(f1, os.Stdout) + }, + } + + errCheckNotPresent = errors.New("requested check not present") + errInvalidScore = errors.New("invalid score") +) + +// countScores quantizes the scores into 12 buckets, from [-1, 10] +// If a check is provided, that check score is used, otherwise the aggregate score is used. +func countScores(input io.Reader, check string) ([12]int, error) { + var counts [12]int // [-1, 10] inclusive + var score int + scanner := bufio.NewScanner(input) + scanner.Buffer(nil, maxResultSize) + for scanner.Scan() { + result, aggregateScore, err := pkg.ExperimentalFromJSON2(strings.NewReader(scanner.Text())) + if err != nil { + return [12]int{}, fmt.Errorf("parsing result: %w", err) + } + if check == "" { + score = int(aggregateScore) + } else { + i := slices.IndexFunc(result.Checks, func(c checker.CheckResult) bool { + return strings.EqualFold(c.Name, check) + }) + if i == -1 { + return [12]int{}, errCheckNotPresent + } + score = result.Checks[i].Score + } + if score < -1 || score > 10 { + return [12]int{}, errInvalidScore + } + bucket := score + 1 // score of -1 is index 0, score of 0 is index 1, etc. + counts[bucket]++ + } + if err := scanner.Err(); err != nil { + return [12]int{}, fmt.Errorf("parsing golden file: %w", err) + } + return counts, nil +} + +func calcStats(input io.Reader, output io.Writer) error { + counts, err := countScores(input, statsCheck) + if err != nil { + return err + } + name := statsCheck + if name == "" { + name = "Aggregate" + } + summary(name, &counts, output) + return nil +} + +func summary(name string, counts *[12]int, output io.Writer) { + const ( + minWidth = 0 + tabWidth = 4 + padding = 1 + padchar = ' ' + flags = tabwriter.AlignRight + ) + w := tabwriter.NewWriter(output, minWidth, tabWidth, padding, padchar, flags) + fmt.Fprintf(w, "%s Score\tCount\t\n", name) + for i, c := range counts { + scoreBucket := i - 1 + fmt.Fprintf(w, "%d\t%d\t\n", scoreBucket, c) + } + w.Flush() +} diff --git a/cmd/internal/scdiff/app/stats_test.go b/cmd/internal/scdiff/app/stats_test.go new file mode 100644 index 00000000000..7896a80d38a --- /dev/null +++ b/cmd/internal/scdiff/app/stats_test.go @@ -0,0 +1,109 @@ +// Copyright 2023 OpenSSF Scorecard Authors +// +// 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. + +package app + +import ( + "bytes" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func Test_countScores(t *testing.T) { + t.Parallel() + + common := `{"date":"0001-01-01T00:00:00Z","repo":{"name":"repo1"},"score":0,"checks":[{"score":10,"name":"Foo"}]} +{"date":"0001-01-01T00:00:00Z","repo":{"name":"repo2"},"score":-1,"checks":[{"score":9,"name":"Foo"}]} +` + tests := []struct { + name string + check string + results string + want [12]int + wantErr bool + }{ + { + name: "aggregate score used when no check specified", + results: common, + want: [12]int{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + { + name: "check score used when check specified", + check: "Foo", + results: common, + want: [12]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, + }, + { + name: "check name case insensitive", + check: "fOo", + results: common, + want: [12]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, + }, + { + name: "non existent check", + check: "not present", + results: common, + wantErr: true, + }, + { + name: "score outside of [-1, 10] rejected", + results: `{"date":"0001-01-01T00:00:00Z","repo":{"name":"repo1"},"score":12}`, + wantErr: true, + }, + { + name: "result fails to parse", + results: `]}[{}`, + wantErr: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + input := strings.NewReader(tt.results) + got, err := countScores(input, tt.check) + if (err != nil) != tt.wantErr { + t.Fatalf("unexpected error: got %v, wantedErr: %t", err, tt.wantErr) + } + if got != tt.want { + t.Errorf("counts differ: %v", cmp.Diff(got, tt.want)) + } + }) + } +} + +func removeRepeatedSpaces(t *testing.T, s string) string { + t.Helper() + return strings.Join(strings.Fields(s), " ") +} + +func Test_calcStats(t *testing.T) { + t.Parallel() + input := strings.NewReader(`{"date":"0001-01-01T00:00:00Z","repo":{"name":"repo1"},"score":10}`) + var output bytes.Buffer + if err := calcStats(input, &output); err != nil { + t.Fatalf("unexepected error: %v", err) + } + got := output.String() + // this is a bit of a simplification, but keeps the test simple + // without needing to update every minor formatting change. + got = removeRepeatedSpaces(t, got) + want := "10 1" // score 10 should have count 1 + if !strings.Contains(got, want) { + t.Errorf("didn't contain expected count") + } +} diff --git a/cron/internal/cii/main.go b/cron/internal/cii/main.go index 53cea8a3acc..fa8616606da 100644 --- a/cron/internal/cii/main.go +++ b/cron/internal/cii/main.go @@ -29,7 +29,7 @@ import ( "github.com/ossf/scorecard/v4/cron/data" ) -const ciiBaseURL = "https://bestpractices.coreinfrastructure.org/projects.json" +const ciiBaseURL = "https://www.bestpractices.dev/projects.json" type ciiPageResp struct { RepoURL string `json:"repo_url"` diff --git a/docs/checks.md b/docs/checks.md index f73aae54a67..1dbbefa00bc 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -59,7 +59,8 @@ Allowed by Scorecard: Risk: `High` (vulnerable to intentional malicious code injection) This check determines whether a project's default and release branches are -protected with GitHub's [branch protection](https://docs.github.com/en/github/administering-a-repository/defining-the-mergeability-of-pull-requests/about-protected-branches) settings. +protected with GitHub's [branch protection](https://docs.github.com/github/administering-a-repository/defining-the-mergeability-of-pull-requests/about-protected-branches) +or [repository rules](https://docs.github.com/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets) settings. Branch protection allows maintainers to define rules that enforce certain workflows for branches, such as requiring review or passing certain status checks before acceptance into a main branch, or preventing rewriting of @@ -68,8 +69,9 @@ public history. Note: The following settings queried by the Branch-Protection check require an admin token: `DismissStaleReviews`, `EnforceAdmins`, `RequireLastPushApproval`, `RequiresStatusChecks` and `UpToDateBeforeMerge`. If the provided token does not have admin access, the check will query the branch settings accessible to non-admins and provide results based only on these settings. -Even so, we recommend using a non-admin token, which provides a thorough enough -result to meet most user needs. +However, all of these settings are accessible via Repo Rules. `EnforceAdmins` is calculated slightly differently. +This setting is calculated as `false` if any [Bypass Actors](https://docs.github.com/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository#granting-bypass-permissions-for-your-ruleset) + are defined on any rule, regardless of if they are admins. Different types of branch protection protect against different risks: @@ -107,7 +109,6 @@ Note: If Scorecard is run without an administrative access token, the requiremen Tier 1 Requirements (3/10 points): - Prevent force push - Prevent branch deletion - - For administrators: Include administrator for review Tier 2 Requirements (6/10 points): - Require at least 1 reviewer for approval before merging @@ -123,6 +124,7 @@ Tier 4 Requirements (9/10 points): Tier 5 Requirements (10/10 points): - For administrators: Dismiss stale reviews and approvals when new commits are pushed + - For administrators: Include administrator for review GitLab Integration Status: - GitLab associates releases with commits and not with the branch. Releases are ignored in this portion of the scoring. @@ -165,17 +167,17 @@ If a project's system was not detected and you think it should be, please Risk: `Low` (possibly not following security best practices) -This check determines whether the project has earned an [OpenSSF (formerly CII) Best Practices Badge](https://bestpractices.coreinfrastructure.org/) at the passing, silver, or gold level. +This check determines whether the project has earned an [OpenSSF (formerly CII) Best Practices Badge](https://www.bestpractices.dev/) at the passing, silver, or gold level. The OpenSSF Best Practices badge indicates whether or not that the project uses a set of security-focused best development practices for open source software. The check uses the URL for the Git repo and the OpenSSF Best Practices badge API. The OpenSSF Best Practices badge has 3 tiers: passing, silver, and gold. We give -full credit to projects that meet the [gold criteria](https://bestpractices.coreinfrastructure.org/criteria/2), which is a significant achievement for projects and requires multiple developers in the project. +full credit to projects that meet the [gold criteria](https://www.bestpractices.dev/criteria/2), which is a significant achievement for projects and requires multiple developers in the project. Lower scores represent a project that has met the silver criteria, met the passing criteria, or is working to achieve the passing badge, with increasingly more points awarded as more criteria are met. Note that even meeting the passing criteria is a significant achievement. -- [gold badge](https://bestpractices.coreinfrastructure.org/criteria/2): 10 -- [silver badge](https://bestpractices.coreinfrastructure.org/criteria/1): 7 -- [passing badge](https://bestpractices.coreinfrastructure.org/criteria/0): 5 +- [gold badge](https://www.bestpractices.dev/criteria/2): 10 +- [silver badge](https://www.bestpractices.dev/criteria/1): 7 +- [passing badge](https://www.bestpractices.dev/criteria/0): 5 - in progress badge: 2 Some of these criteria overlap with other Scorecard checks. @@ -183,7 +185,7 @@ However, note that in those overlapping cases, Scorecard can only report what it **Remediation steps** -- Sign up for the [OpenSSF Best Practices program](https://bestpractices.coreinfrastructure.org/). +- Sign up for the [OpenSSF Best Practices program](https://www.bestpractices.dev/). ## Code-Review diff --git a/docs/checks/internal/checks.yaml b/docs/checks/internal/checks.yaml index cac762cb5a2..3044618e4c4 100644 --- a/docs/checks/internal/checks.yaml +++ b/docs/checks/internal/checks.yaml @@ -148,7 +148,8 @@ checks: Risk: `High` (vulnerable to intentional malicious code injection) This check determines whether a project's default and release branches are - protected with GitHub's [branch protection](https://docs.github.com/en/github/administering-a-repository/defining-the-mergeability-of-pull-requests/about-protected-branches) settings. + protected with GitHub's [branch protection](https://docs.github.com/github/administering-a-repository/defining-the-mergeability-of-pull-requests/about-protected-branches) + or [repository rules](https://docs.github.com/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets) settings. Branch protection allows maintainers to define rules that enforce certain workflows for branches, such as requiring review or passing certain status checks before acceptance into a main branch, or preventing rewriting of @@ -157,8 +158,9 @@ checks: Note: The following settings queried by the Branch-Protection check require an admin token: `DismissStaleReviews`, `EnforceAdmins`, `RequireLastPushApproval`, `RequiresStatusChecks` and `UpToDateBeforeMerge`. If the provided token does not have admin access, the check will query the branch settings accessible to non-admins and provide results based only on these settings. - Even so, we recommend using a non-admin token, which provides a thorough enough - result to meet most user needs. + However, all of these settings are accessible via Repo Rules. `EnforceAdmins` is calculated slightly differently. + This setting is calculated as `false` if any [Bypass Actors](https://docs.github.com/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository#granting-bypass-permissions-for-your-ruleset) + are defined on any rule, regardless of if they are admins. Different types of branch protection protect against different risks: @@ -196,7 +198,6 @@ checks: Tier 1 Requirements (3/10 points): - Prevent force push - Prevent branch deletion - - For administrators: Include administrator for review Tier 2 Requirements (6/10 points): - Require at least 1 reviewer for approval before merging @@ -212,6 +213,7 @@ checks: Tier 5 Requirements (10/10 points): - For administrators: Dismiss stale reviews and approvals when new commits are pushed + - For administrators: Include administrator for review GitLab Integration Status: - GitLab associates releases with commits and not with the branch. Releases are ignored in this portion of the scoring. @@ -263,24 +265,24 @@ checks: description: | Risk: `Low` (possibly not following security best practices) - This check determines whether the project has earned an [OpenSSF (formerly CII) Best Practices Badge](https://bestpractices.coreinfrastructure.org/) at the passing, silver, or gold level. + This check determines whether the project has earned an [OpenSSF (formerly CII) Best Practices Badge](https://www.bestpractices.dev/) at the passing, silver, or gold level. The OpenSSF Best Practices badge indicates whether or not that the project uses a set of security-focused best development practices for open source software. The check uses the URL for the Git repo and the OpenSSF Best Practices badge API. The OpenSSF Best Practices badge has 3 tiers: passing, silver, and gold. We give - full credit to projects that meet the [gold criteria](https://bestpractices.coreinfrastructure.org/criteria/2), which is a significant achievement for projects and requires multiple developers in the project. + full credit to projects that meet the [gold criteria](https://www.bestpractices.dev/criteria/2), which is a significant achievement for projects and requires multiple developers in the project. Lower scores represent a project that has met the silver criteria, met the passing criteria, or is working to achieve the passing badge, with increasingly more points awarded as more criteria are met. Note that even meeting the passing criteria is a significant achievement. - - [gold badge](https://bestpractices.coreinfrastructure.org/criteria/2): 10 - - [silver badge](https://bestpractices.coreinfrastructure.org/criteria/1): 7 - - [passing badge](https://bestpractices.coreinfrastructure.org/criteria/0): 5 + - [gold badge](https://www.bestpractices.dev/criteria/2): 10 + - [silver badge](https://www.bestpractices.dev/criteria/1): 7 + - [passing badge](https://www.bestpractices.dev/criteria/0): 5 - in progress badge: 2 Some of these criteria overlap with other Scorecard checks. However, note that in those overlapping cases, Scorecard can only report what it can automatically detect, while the OpenSSF Best Practices badge can report on claims and claim justifications from people (this counters false negatives and positives but has the challenge of requiring additional work from people). remediation: - >- - Sign up for the [OpenSSF Best Practices program](https://bestpractices.coreinfrastructure.org/). + Sign up for the [OpenSSF Best Practices program](https://www.bestpractices.dev/). Code-Review: risk: High tags: supply-chain, security, source-code, code-reviews diff --git a/docs/faq.md b/docs/faq.md index 2fd5f6ffb8a..2ae17986580 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -25,7 +25,11 @@ This page answers frequently asked questions about Scorecard, including its purp Yes. -Over a million projects are automatically tracked by the Scorecard project. These projects' scores can be seen at https://api.securityscorecards.dev/projects/github.com//. +Over a million projects are automatically tracked by the Scorecard project. Use the webviewer to see these scores, replacing the placeholder text with the platform, user/org, and repository name: https://securityscorecards.dev/viewer/?uri=.com//. + +For example: + - [https://securityscorecards.dev/viewer/?uri=github.com/ossf/scorecard](https://securityscorecards.dev/viewer/?uri=github.com/ossf/scorecard) + - [https://securityscorecards.dev/viewer/?uri=gitlab.com/fdroid/fdroidclient](https://securityscorecards.dev/viewer/?uri=gitlab.com/fdroid/fdroidclient) You can also use the CLI to generate scores for any public repository by following these steps: diff --git a/e2e/branch_protection_test.go b/e2e/branch_protection_test.go index 7a386273346..114412d1158 100644 --- a/e2e/branch_protection_test.go +++ b/e2e/branch_protection_test.go @@ -143,3 +143,34 @@ var _ = Describe("E2E TEST GITHUB_TOKEN:"+checks.CheckBranchProtection, func() { }) }) }) + +var _ = Describe("E2E TEST:"+checks.CheckBranchProtection+" (repo rules)", func() { + Context("E2E TEST:Validating branch protection with repo rules", func() { + It("Should be able to read repo rules", func() { + dl := scut.TestDetailLogger{} + // no force push, no deletion, no bypass, dismiss stale reviews + repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-repo-rules-e2e") + Expect(err).Should(BeNil()) + repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) + err = repoClient.InitRepo(repo, clients.HeadSHA, 0) + Expect(err).Should(BeNil()) + req := checker.CheckRequest{ + Ctx: context.Background(), + RepoClient: repoClient, + Repo: repo, + Dlogger: &dl, + } + expected := scut.TestReturn{ + Error: nil, + Score: 3, + NumberOfWarn: 2, + NumberOfInfo: 4, + NumberOfDebug: 2, + } + result := checks.BranchProtection(&req) + Expect(result.Error).Should(BeNil()) + Expect(scut.ValidateTestReturn(nil, "repo rules accessible", &expected, &result, &dl)).Should(BeTrue()) + Expect(repoClient.Close()).Should(BeNil()) + }) + }) +}) diff --git a/e2e/dependency_update_tool_test.go b/e2e/dependency_update_tool_test.go index 836f5c960e0..a580a6bb933 100644 --- a/e2e/dependency_update_tool_test.go +++ b/e2e/dependency_update_tool_test.go @@ -48,7 +48,7 @@ var _ = Describe("E2E TEST:"+checks.CheckDependencyUpdateTool, func() { expected := scut.TestReturn{ Error: nil, Score: checker.MaxResultScore, - NumberOfWarn: 3, + NumberOfWarn: 0, NumberOfInfo: 1, NumberOfDebug: 0, } @@ -75,7 +75,7 @@ var _ = Describe("E2E TEST:"+checks.CheckDependencyUpdateTool, func() { expected := scut.TestReturn{ Error: nil, Score: checker.MaxResultScore, - NumberOfWarn: 3, + NumberOfWarn: 0, NumberOfInfo: 1, NumberOfDebug: 0, } diff --git a/e2e/fuzzing_test.go b/e2e/fuzzing_test.go index 6d33e84cde6..cf94f297688 100644 --- a/e2e/fuzzing_test.go +++ b/e2e/fuzzing_test.go @@ -25,6 +25,7 @@ import ( "github.com/ossf/scorecard/v4/checks/raw" "github.com/ossf/scorecard/v4/clients" "github.com/ossf/scorecard/v4/clients/githubrepo" + "github.com/ossf/scorecard/v4/clients/gitlabrepo" "github.com/ossf/scorecard/v4/clients/ossfuzz" scut "github.com/ossf/scorecard/v4/utests" ) @@ -51,7 +52,39 @@ var _ = Describe("E2E TEST:"+checks.CheckFuzzing, func() { expected := scut.TestReturn{ Error: nil, Score: checker.MaxResultScore, - NumberOfWarn: 6, + NumberOfWarn: 0, + NumberOfInfo: 1, + NumberOfDebug: 0, + } + result := checks.Fuzzing(&req) + Expect(scut.ValidateTestReturn(nil, "use fuzzing", &expected, &result, &dl)).Should(BeTrue()) + Expect(repoClient.Close()).Should(BeNil()) + Expect(ossFuzzRepoClient.Close()).Should(BeNil()) + }) + It("Should return use of OSS-Fuzz - GitLab", func() { + dl := scut.TestDetailLogger{} + repo, err := gitlabrepo.MakeGitlabRepo("gitlab.com/libtiff/libtiff") + Expect(err).Should(BeNil()) + + repoClient, err := gitlabrepo.CreateGitlabClient(context.Background(), repo.Host()) + Expect(err).Should(BeNil()) + err = repoClient.InitRepo(repo, clients.HeadSHA, 0) + + Expect(err).Should(BeNil()) + ossFuzzRepoClient, err := ossfuzz.CreateOSSFuzzClientEager(ossfuzz.StatusURL) + Expect(err).Should(BeNil()) + + req := checker.CheckRequest{ + Ctx: context.Background(), + RepoClient: repoClient, + OssFuzzRepo: ossFuzzRepoClient, + Repo: repo, + Dlogger: &dl, + } + expected := scut.TestReturn{ + Error: nil, + Score: checker.MaxResultScore, + NumberOfWarn: 0, NumberOfInfo: 1, NumberOfDebug: 0, } @@ -80,7 +113,7 @@ var _ = Describe("E2E TEST:"+checks.CheckFuzzing, func() { expected := scut.TestReturn{ Error: nil, Score: checker.MaxResultScore, - NumberOfWarn: 6, + NumberOfWarn: 0, NumberOfInfo: 1, NumberOfDebug: 0, } @@ -109,7 +142,7 @@ var _ = Describe("E2E TEST:"+checks.CheckFuzzing, func() { expected := scut.TestReturn{ Error: nil, Score: checker.MaxResultScore, - NumberOfWarn: 6, + NumberOfWarn: 0, NumberOfInfo: 2, NumberOfDebug: 0, } diff --git a/e2e/pinned_dependencies_test.go b/e2e/pinned_dependencies_test.go index c49357d337e..924a43a1ce1 100644 --- a/e2e/pinned_dependencies_test.go +++ b/e2e/pinned_dependencies_test.go @@ -49,9 +49,9 @@ var _ = Describe("E2E TEST:"+checks.CheckPinnedDependencies, func() { } expected := scut.TestReturn{ Error: nil, - Score: 4, + Score: 2, NumberOfWarn: 139, - NumberOfInfo: 3, + NumberOfInfo: 5, NumberOfDebug: 0, } result := checks.PinningDependencies(&req) @@ -74,9 +74,9 @@ var _ = Describe("E2E TEST:"+checks.CheckPinnedDependencies, func() { } expected := scut.TestReturn{ Error: nil, - Score: 4, + Score: 2, NumberOfWarn: 139, - NumberOfInfo: 3, + NumberOfInfo: 5, NumberOfDebug: 0, } result := checks.PinningDependencies(&req) @@ -110,9 +110,9 @@ var _ = Describe("E2E TEST:"+checks.CheckPinnedDependencies, func() { } expected := scut.TestReturn{ Error: nil, - Score: 4, + Score: 2, NumberOfWarn: 139, - NumberOfInfo: 3, + NumberOfInfo: 5, NumberOfDebug: 0, } result := checks.PinningDependencies(&req) diff --git a/go.mod b/go.mod index a92c0988d75..3806ebccd59 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,14 @@ module github.com/ossf/scorecard/v4 go 1.19 require ( - github.com/rhysd/actionlint v1.6.15 - gotest.tools v2.2.0+incompatible -) - -require ( - cloud.google.com/go/bigquery v1.54.0 + cloud.google.com/go/bigquery v1.55.0 cloud.google.com/go/monitoring v1.15.1 // indirect cloud.google.com/go/pubsub v1.33.0 cloud.google.com/go/trace v1.10.1 // indirect contrib.go.opencensus.io/exporter/stackdriver v0.13.14 github.com/bombsimon/logrusr/v2 v2.0.1 - github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 - github.com/go-git/go-git/v5 v5.8.1 + github.com/bradleyfalzon/ghinstallation/v2 v2.7.0 + github.com/go-git/go-git/v5 v5.9.0 github.com/go-logr/logr v1.2.4 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.5.9 @@ -26,6 +21,7 @@ require ( github.com/moby/buildkit v0.12.2 github.com/olekukonko/tablewriter v0.0.5 github.com/onsi/gomega v1.27.10 + github.com/rhysd/actionlint v1.6.15 github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a github.com/sirupsen/logrus v1.9.3 @@ -34,7 +30,7 @@ require ( go.opencensus.io v0.24.0 gocloud.dev v0.34.0 golang.org/x/text v0.13.0 - golang.org/x/tools v0.12.0 // indirect + golang.org/x/tools v0.13.0 // indirect google.golang.org/genproto v0.0.0-20230731193218-e0aa005b6bdf // indirect google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v2 v2.4.0 @@ -47,9 +43,9 @@ require ( github.com/caarlos0/env/v6 v6.10.0 github.com/gobwas/glob v0.2.3 github.com/google/go-github/v53 v53.2.0 - github.com/google/osv-scanner v1.3.6 + github.com/google/osv-scanner v1.4.0 github.com/mcuadros/go-jsonschema-generator v0.0.0-20200330054847-ba7a369d4303 - github.com/onsi/ginkgo/v2 v2.12.0 + github.com/onsi/ginkgo/v2 v2.12.1 github.com/otiai10/copy v1.12.0 sigs.k8s.io/release-utils v0.6.0 ) @@ -60,13 +56,14 @@ require ( cloud.google.com/go/kms v1.15.0 // indirect dario.cat/mergo v1.0.0 // indirect github.com/BurntSushi/toml v1.3.2 // indirect - github.com/CycloneDX/cyclonedx-go v0.7.1 // indirect + github.com/CycloneDX/cyclonedx-go v0.7.2 // indirect github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/apache/arrow/go/v12 v12.0.0 // indirect github.com/apache/thrift v0.16.0 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.10.2 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect @@ -80,12 +77,14 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/flatbuffers v2.0.8+incompatible // indirect github.com/google/gnostic v0.6.9 // indirect + github.com/google/go-github/v55 v55.0.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect github.com/google/s2a-go v0.1.4 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.4 // indirect - github.com/jedib0t/go-pretty/v6 v6.4.6 // indirect + github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab // indirect + github.com/jedib0t/go-pretty/v6 v6.4.7 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/asmfmt v1.3.2 // indirect @@ -96,13 +95,14 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/owenrumney/go-sarif/v2 v2.2.0 // indirect github.com/package-url/packageurl-go v0.1.1 // indirect github.com/pierrec/lz4/v4 v4.1.15 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/prometheus/prometheus v0.46.0 // indirect github.com/skeema/knownhosts v1.2.0 // indirect github.com/spdx/gordf v0.0.0-20221230105357-b735bd5aac89 // indirect - github.com/spdx/tools-golang v0.5.2 // indirect + github.com/spdx/tools-golang v0.5.3 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/term v0.12.0 // indirect @@ -128,7 +128,7 @@ require ( cloud.google.com/go/iam v1.1.1 // indirect cloud.google.com/go/storage v1.31.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/aws/aws-sdk-go v1.44.314 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect @@ -141,7 +141,7 @@ require ( github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.15.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.4.1 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -169,12 +169,12 @@ require ( github.com/sergi/go-diff v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/vbatts/tar-split v0.11.3 // indirect - github.com/xanzy/go-gitlab v0.91.1 + github.com/xanzy/go-gitlab v0.92.1 github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect golang.org/x/crypto v0.13.0 // indirect - golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/net v0.15.0 // indirect golang.org/x/oauth2 v0.12.0 golang.org/x/sync v0.3.0 // indirect @@ -185,25 +185,3 @@ require ( google.golang.org/grpc v1.57.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) - -replace ( - // https://deps.dev/advisory/OSV/GO-2021-0057?from=%2Fgo%2Fgithub.com%252Fbuger%252Fjsonparser%2Fv1.0.0 - github.com/buger/jsonparser => github.com/buger/jsonparser v1.1.1 - // https://deps.dev/advisory/OSV/GO-2020-0017?from=%2Fgo%2Fk8s.io%252Fclient-go%2Fv0.0.0-20200207030105-473926661c44 - github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c => github.com/golang-jwt/jwt v3.2.1+incompatible - github.com/dgrijalva/jwt-go v3.2.0+incompatible => github.com/golang-jwt/jwt v3.2.1+incompatible - // This replace is for GHSA-qq97-vm5h-rrhg and GHSA-hqxw-f8mx-cpmw - github.com/docker/distribution => github.com/docker/distribution v2.8.2+incompatible - // This replace is for https://osv.dev/vulnerability/GHSA-r48q-9g5r-8q2h - github.com/emicklei/go-restful => github.com/emicklei/go-restful v2.16.0+incompatible - // https://go.googlesource.com/vulndb/+/refs/heads/master/reports/GO-2020-0020.yaml - github.com/gorilla/handlers => github.com/gorilla/handlers v1.3.0 - // https://github.com/miekg/dns/issues/1037 - github.com/miekg/dns => github.com/miekg/dns v1.1.25-0.20191211073109-8ebf2e419df7 - //https://github.com/opencontainers/distribution-spec/security/advisories/GHSA-mc8v-mgrf-8f4m - github.com/opencontainers/image-spec => github.com/opencontainers/image-spec v1.0.2 - // This replace is for https://nvd.nist.gov/vuln/detail/CVE-2021-3538 - github.com/satori/go.uuid => github.com/satori/go.uuid v1.2.1-0.20181016170032-d91630c85102 - // This replace is for https://github.com/advisories/GHSA-25xm-hr59-7c27 - github.com/ulikunitz/xz => github.com/ulikunitz/xz v0.5.8 -) diff --git a/go.sum b/go.sum index ad479140f56..08180fcf52b 100644 --- a/go.sum +++ b/go.sum @@ -14,11 +14,10 @@ cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5x cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.54.0 h1:ify6s7sy+kQuAimRnVTrPUzaeY0+X5GEsKt2C5CiA8w= -cloud.google.com/go/bigquery v1.54.0/go.mod h1:9Y5I3PN9kQWuid6183JFhOGOW3GcirA5LpsKCUn+2ec= +cloud.google.com/go/bigquery v1.55.0 h1:hs44Xxov3XLWQiCx2J8lK5U/ihLqnpm4RVVl5fdtLLI= +cloud.google.com/go/bigquery v1.55.0/go.mod h1:9Y5I3PN9kQWuid6183JFhOGOW3GcirA5LpsKCUn+2ec= cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= -cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/containeranalysis v0.10.1 h1:SM/ibWHWp4TYyJMwrILtcBtYKObyupwOVeceI9pNblw= @@ -82,8 +81,8 @@ github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CycloneDX/cyclonedx-go v0.7.1 h1:5w1SxjGm9MTMNTuRbEPyw21ObdbaagTWF/KfF0qHTRE= -github.com/CycloneDX/cyclonedx-go v0.7.1/go.mod h1:N/nrdWQI2SIjaACyyDs/u7+ddCkyl/zkNs8xFsHF2Ps= +github.com/CycloneDX/cyclonedx-go v0.7.2 h1:kKQ0t1dPOlugSIYVOMiMtFqeXI2wp/f5DBIdfux8gnQ= +github.com/CycloneDX/cyclonedx-go v0.7.2/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk= github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c h1:RGWPOewvKIROun94nF7v2cua9qP+thov/7M50KEoeSU= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= @@ -95,8 +94,8 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= -github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs= -github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= @@ -115,6 +114,7 @@ github.com/apache/arrow/go/v12 v12.0.0 h1:xtZE63VWl7qLdB0JObIXvvhGjoVNrQ9ciIHG2O github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -150,8 +150,8 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM= github.com/bombsimon/logrusr/v2 v2.0.1/go.mod h1:ByVAX+vHdLGAfdroiMg6q0zgq2FODY2lc5YJvzmOJio= -github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 h1:IRY7Xy588KylkoycsUhFpW7cdGpy5Y5BPsz4IfuJtGk= -github.com/bradleyfalzon/ghinstallation/v2 v2.6.0/go.mod h1:oQ3etOwN3TRH4EwgW5/7MxSVMGlMlzG/O8TU7eYdoSk= +github.com/bradleyfalzon/ghinstallation/v2 v2.7.0 h1:ranXaC3Zz/F6G/f0Joj3LrFp2OzOKfJZev5Q7OaMc88= +github.com/bradleyfalzon/ghinstallation/v2 v2.7.0/go.mod h1:ymxfmloxXBFXvvF1KpeUhOQM6Dfz9NYtfvTiJyk82UE= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= @@ -200,17 +200,21 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v24.0.4+incompatible h1:Y3bYF9ekNTm2VFz5U/0BlMdJy73D+Y1iAAZ8l63Ydzw= github.com/docker/cli v24.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= @@ -227,8 +231,9 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= -github.com/emicklei/go-restful v2.16.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE= github.com/emicklei/go-restful/v3 v3.10.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -256,11 +261,11 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= -github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A= -github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo= +github.com/go-git/go-git/v5 v5.9.0 h1:cD9SFA7sHVRdJ7AYck1ZaAa/yeuBvGPxwXDL8cxrObY= +github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2s/OsZWE0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -309,7 +314,6 @@ github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -335,6 +339,7 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -364,7 +369,6 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/google/go-containerregistry v0.2.1/go.mod h1:Ts3Wioz1r5ayWx8sS6vLcWltWcM1aqFjd/eVrkFhrWM= @@ -372,6 +376,8 @@ github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYd github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI= github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao= +github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg= +github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE= @@ -384,8 +390,8 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= -github.com/google/osv-scanner v1.3.6 h1:8oZxk2HfdMjsLZ/NIidEEb2srcIP8301hKbR+FPXaPo= -github.com/google/osv-scanner v1.3.6/go.mod h1:Yl29UCfOjWRA/wQLmvK0Ehs7HztmRqtgvHa3SomAJFo= +github.com/google/osv-scanner v1.4.0 h1:9b97UMAD0FKZRpeYpZ5A9azsZZfsjDIx0qe24xxU1CU= +github.com/google/osv-scanner v1.4.0/go.mod h1:OZAKw86Wory8cAX9MFrvHdP7S85oJySGyD/jHxNSS+c= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -459,6 +465,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe6ZTd9F8kXETBoijjFJ/ntaa//1wiH9BZu4zU= +github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= @@ -467,8 +475,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw= -github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= +github.com/jedib0t/go-pretty/v6 v6.4.7 h1:lwiTJr1DEkAgzljsUsORmWsVn5MQjt1BPJdPCtJ6KXE= +github.com/jedib0t/go-pretty/v6 v6.4.7/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= @@ -543,7 +551,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mcuadros/go-jsonschema-generator v0.0.0-20200330054847-ba7a369d4303 h1:mc6Th1b2xkPDUHTIUynE0LMJUgPEJdIDUjBLvj8yprs= github.com/mcuadros/go-jsonschema-generator v0.0.0-20200330054847-ba7a369d4303/go.mod h1:O6IeMrJ2EU+kDaxu7Dchbd0fbmrsTcjg8SGYFVJCr5A= -github.com/miekg/dns v1.1.25-0.20191211073109-8ebf2e419df7/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= @@ -586,8 +594,8 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= -github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= +github.com/onsi/ginkgo/v2 v2.12.1 h1:uHNEO1RP2SpuZApSkel9nEh1/Mu+hmQe7Q+Pepg5OYA= +github.com/onsi/ginkgo/v2 v2.12.1/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -600,11 +608,15 @@ github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3ev github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= +github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY= github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= +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/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -650,12 +662,12 @@ github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfm github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/satori/go.uuid v1.2.1-0.20181016170032-d91630c85102/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= @@ -683,8 +695,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= github.com/spdx/gordf v0.0.0-20221230105357-b735bd5aac89 h1:dArkMwZ7Mf2JiU8OfdmqIv8QaHT4oyifLIe1UhsF1SY= github.com/spdx/gordf v0.0.0-20221230105357-b735bd5aac89/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= -github.com/spdx/tools-golang v0.5.2 h1:dtMNjJreWPe37584ajk7m/rQtfJaLpRMk7pUGgvekOg= -github.com/spdx/tools-golang v0.5.2/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI= +github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY= +github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -721,6 +733,7 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -729,9 +742,11 @@ github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= github.com/vdemeester/k8s-pkg-credentialprovider v1.18.1-0.20201019120933-f1d16962a4db/go.mod h1:grWy0bkr1XO6hqbaaCKaPXqkBVlMGHYG6PGykktwbJc= +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/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= -github.com/xanzy/go-gitlab v0.91.1 h1:gnV57IPGYywWer32oXKBcdmc8dVxeKl3AauV8Bu17rw= -github.com/xanzy/go-gitlab v0.91.1/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= +github.com/xanzy/go-gitlab v0.92.1 h1:4HfRQtGtGd1M/Xn3G6hOikfWaysL7/G6y4EEzVKINPs= +github.com/xanzy/go-gitlab v0.92.1/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= 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/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= @@ -746,6 +761,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 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= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= @@ -772,7 +788,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -784,6 +799,7 @@ golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -798,8 +814,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -847,13 +863,13 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/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-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -877,7 +893,6 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -917,8 +932,6 @@ golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -956,6 +969,7 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -967,6 +981,7 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -983,6 +998,7 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1013,7 +1029,6 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1039,8 +1054,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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= @@ -1134,7 +1149,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= diff --git a/pkg/json.go b/pkg/json.go index 3257e91dc6b..1382cc9f086 100644 --- a/pkg/json.go +++ b/pkg/json.go @@ -176,17 +176,18 @@ func (r *ScorecardResult) AsJSON2(showDetails bool, return nil } -// This function is experimental. Do not depend on it, it may be removed at any point. -func ExperimentalFromJSON2(r io.Reader) (ScorecardResult, error) { +// ExperimentalFromJSON2 is experimental. Do not depend on it, it may be removed at any point. +// Also returns the aggregate score, as the ScorecardResult field does not contain it. +func ExperimentalFromJSON2(r io.Reader) (result ScorecardResult, score float64, err error) { var jsr JSONScorecardResultV2 decoder := json.NewDecoder(r) if err := decoder.Decode(&jsr); err != nil { - return ScorecardResult{}, fmt.Errorf("decode json: %w", err) + return ScorecardResult{}, 0, fmt.Errorf("decode json: %w", err) } date, err := time.Parse(time.RFC3339, jsr.Date) if err != nil { - return ScorecardResult{}, fmt.Errorf("parse scorecard analysis time: %w", err) + return ScorecardResult{}, 0, fmt.Errorf("parse scorecard analysis time: %w", err) } sr := ScorecardResult{ @@ -216,7 +217,7 @@ func ExperimentalFromJSON2(r io.Reader) (ScorecardResult, error) { sr.Checks = append(sr.Checks, cr) } - return sr, nil + return sr, float64(jsr.AggregateScore), nil } func (r *ScorecardResult) AsFJSON(showDetails bool, diff --git a/pkg/scorecard.go b/pkg/scorecard.go index 61ef10375bf..b8c1a4e1691 100644 --- a/pkg/scorecard.go +++ b/pkg/scorecard.go @@ -35,6 +35,9 @@ import ( "github.com/ossf/scorecard/v4/probes/zrunner" ) +// errEmptyRepository indicates the repository is empty. +var errEmptyRepository = errors.New("repository empty") + func runEnabledChecks(ctx context.Context, repo clients.Repo, raw *checker.RawResults, checksToRun checker.CheckNameToFnMap, repoClient clients.RepoClient, ossFuzzRepoClient clients.RepoClient, ciiClient clients.CIIBestPracticesClient, @@ -80,10 +83,10 @@ func getRepoCommitHash(r clients.RepoClient) (string, error) { return "", sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("ListCommits:%v", err.Error())) } - if len(commits) > 0 { - return commits[0].SHA, nil + if len(commits) == 0 { + return "", errEmptyRepository } - return "", nil + return commits[0].SHA, nil } // RunScorecard runs enabled Scorecard checks on a Repo. @@ -104,19 +107,6 @@ func RunScorecard(ctx context.Context, } defer repoClient.Close() - commitSHA, err := getRepoCommitHash(repoClient) - if err != nil || commitSHA == "" { - return ScorecardResult{}, err - } - defaultBranch, err := repoClient.GetDefaultBranchName() - if err != nil { - if !errors.Is(err, clients.ErrUnsupportedFeature) { - return ScorecardResult{}, - sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("GetDefaultBranchName:%v", err.Error())) - } - defaultBranch = "unknown" - } - versionInfo := version.GetVersionInfo() ret := ScorecardResult{ Repo: RepoInfo{ @@ -129,6 +119,25 @@ func RunScorecard(ctx context.Context, }, Date: time.Now(), } + + commitSHA, err := getRepoCommitHash(repoClient) + + if errors.Is(err, errEmptyRepository) { + return ret, nil + } else if err != nil { + return ScorecardResult{}, err + } + ret.Repo.CommitSHA = commitSHA + + defaultBranch, err := repoClient.GetDefaultBranchName() + if err != nil { + if !errors.Is(err, clients.ErrUnsupportedFeature) { + return ScorecardResult{}, + sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("GetDefaultBranchName:%v", err.Error())) + } + defaultBranch = "unknown" + } + resultsCh := make(chan checker.CheckResult) // Set metadata for all checks to use. This is necessary diff --git a/pkg/scorecard_test.go b/pkg/scorecard_test.go index f2d47e2b314..76a4ced138b 100644 --- a/pkg/scorecard_test.go +++ b/pkg/scorecard_test.go @@ -15,10 +15,11 @@ package pkg import ( "context" - "reflect" "testing" "github.com/golang/mock/gomock" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/ossf/scorecard/v4/clients" "github.com/ossf/scorecard/v4/clients/localdir" @@ -41,7 +42,7 @@ func Test_getRepoCommitHash(t *testing.T) { { name: "empty commit", want: "", - wantErr: false, + wantErr: true, }, } for _, tt := range tests { @@ -121,6 +122,7 @@ func Test_getRepoCommitHashLocal(t *testing.T) { func TestRunScorecard(t *testing.T) { t.Parallel() type args struct { + uri string commitSHA string } tests := []struct { @@ -130,11 +132,19 @@ func TestRunScorecard(t *testing.T) { wantErr bool }{ { - name: "empty commits repos should return empty result", + name: "empty commits repos should return repo details but no checks", args: args{ + uri: "github.com/ossf/scorecard", commitSHA: "", }, - want: ScorecardResult{}, + want: ScorecardResult{ + Repo: RepoInfo{ + Name: "github.com/ossf/scorecard", + }, + Scorecard: ScorecardInfo{ + CommitSHA: "unknown", + }, + }, wantErr: false, }, } @@ -146,6 +156,8 @@ func TestRunScorecard(t *testing.T) { mockRepoClient := mockrepo.NewMockRepoClient(ctrl) repo := mockrepo.NewMockRepo(ctrl) + repo.EXPECT().URI().Return(tt.args.uri).AnyTimes() + mockRepoClient.EXPECT().InitRepo(repo, tt.args.commitSHA, 0).Return(nil) mockRepoClient.EXPECT().Close().DoAndReturn(func() error { @@ -168,8 +180,9 @@ func TestRunScorecard(t *testing.T) { t.Errorf("RunScorecard() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("RunScorecard() got = %v, want %v", got, tt.want) + ignoreDate := cmpopts.IgnoreFields(ScorecardResult{}, "Date") + if !cmp.Equal(got, tt.want, ignoreDate) { + t.Errorf("expected %v, got %v", got, cmp.Diff(tt.want, got, ignoreDate)) } }) } diff --git a/tools/go.mod b/tools/go.mod index ae4ac120c85..9bfc64d538c 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -9,7 +9,7 @@ require ( github.com/google/ko v0.14.1 github.com/goreleaser/goreleaser v1.20.0 github.com/naveensrinivasan/stunning-tribble v0.4.2 - github.com/onsi/ginkgo/v2 v2.12.0 + github.com/onsi/ginkgo/v2 v2.12.1 google.golang.org/protobuf v1.31.0 ) @@ -362,7 +362,7 @@ require ( golang.org/x/net v0.14.0 // indirect golang.org/x/oauth2 v0.11.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.11.0 // indirect + golang.org/x/sys v0.12.0 // indirect golang.org/x/term v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect golang.org/x/time v0.3.0 // indirect @@ -393,8 +393,3 @@ require ( sigs.k8s.io/kind v0.20.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) - -replace ( - github.com/sigstore/cosign => github.com/sigstore/cosign v1.12.0 - github.com/theupdateframework/go-tuf => github.com/theupdateframework/go-tuf v0.3.0 -) diff --git a/tools/go.sum b/tools/go.sum index 8b480d8b340..1f7f11c22bd 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -313,7 +313,6 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= @@ -365,7 +364,6 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk= github.com/elliotchance/orderedmap/v2 v2.2.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q= @@ -393,7 +391,6 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= -github.com/flynn/go-docopt v0.0.0-20140912013429-f6dd2ebbb31e/go.mod h1:HyVoz1Mz5Co8TFO8EupIdlcpwShBmY98dkT2xeHkvEI= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -557,7 +554,6 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= @@ -885,17 +881,14 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= -github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= +github.com/onsi/ginkgo/v2 v2.12.1 h1:uHNEO1RP2SpuZApSkel9nEh1/Mu+hmQe7Q+Pepg5OYA= +github.com/onsi/ginkgo/v2 v2.12.1/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -987,7 +980,6 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.24.0 h1:MKNzmXtGh5N0y74Z/CIaJh4GlB364l0K1RUT08WSWAc= github.com/sashamelentyev/usestdlibvars v1.24.0/go.mod h1:9cYkq+gYJ+a5W2RPdhfaSCnTVUC1OQP/bSiiBhq3OZE= -github.com/secure-systems-lab/go-securesystemslib v0.3.1/go.mod h1:o8hhjkbNl2gOamKUA/eNW3xUrntHT9L4W89W1nfj43U= github.com/securego/gosec/v2 v2.17.0 h1:ZpAStTDKY39insEG9OH6kV3IkhQZPTq9a9eGOLOjcdI= github.com/securego/gosec/v2 v2.17.0/go.mod h1:lt+mgC91VSmriVoJLentrMkRCYs+HLTBnUFUBuhV2hc= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= @@ -1070,7 +1062,6 @@ 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/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplBwWcHBo6q9xrfWdMrT9o4kltkmmvpemgIjep/8= github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c/go.mod h1:SbErYREK7xXdsRiigaQiQkI9McGRzYMvlKYaP3Nimdk= github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= @@ -1083,8 +1074,8 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.4.14 h1:ScO641OHpf9UpHPk8fCknSuXNMpi4iFlwuWoBs3L+1s= github.com/tetafro/godot v1.4.14/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= -github.com/theupdateframework/go-tuf v0.3.0 h1:od2sc5+BSkKZhmUG2o2rmruy0BGSmhrbDhCnpxh87X8= -github.com/theupdateframework/go-tuf v0.3.0/go.mod h1:E5XP0wXitrFUHe4b8cUcAAdxBW4LbfnqF4WXXGLgWNo= +github.com/theupdateframework/go-tuf v0.5.2 h1:habfDzTmpbzBLIFGWa2ZpVhYvFBoK0C1onC3a4zuPRA= +github.com/theupdateframework/go-tuf v0.5.2/go.mod h1:SyMV5kg5n4uEclsyxXJZI2UxPFJNDc4Y+r7wv+MlvTA= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= @@ -1185,7 +1176,6 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -1274,7 +1264,6 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -1288,7 +1277,6 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= @@ -1361,12 +1349,10 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1402,8 +1388,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=