diff --git a/README.md b/README.md index bda6efed5219..b876a764daeb 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ - [Default Scorecard Checks](#scorecard-checks) - [Detailed Check Documentation](docs/checks.md) (Scoring Criteria, Risks, and Remediation) +- [Beginner's Guide to Scorecard Checks](#beginners-guide-to-scorecard-checks) ## Other Important Recommendations - [Two-factor Authentication (2FA)](#two-factor-authentication-2fa) @@ -94,14 +95,14 @@ metrics. Prominent projects that use Scorecard include: ### View a Project's Score -To see scores for projects regularly scanned by Scorecard, navigate to the webviewer, replacing the placeholder text with the platform, user/org, and repository name: +To see scores for projects regularly 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: +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). +To view scores for projects not included in the webviewer, use the [Scorecard CLI](#scorecard-command-line-interface). ### Public Data @@ -506,6 +507,10 @@ Name | Description | Risk Level | Token Req To see detailed information about each check, its scoring criteria, and remediation steps, check out the [checks documentation page](docs/checks.md). +### Beginner's Guide to Scorecard Checks + +For a guide to the checks you should use when getting started, see the [beginner's guide to scorecard checks](docs/beginner-checks.md). + ## Other Important Recommendations ### Two-factor Authentication (2FA) @@ -593,13 +598,13 @@ To report a security issue, please follow instructions [here](SECURITY.md). ### Join the Scorecards Project Meeting -#### Zoom +#### Zoom -We meet every other Thursday - 4p ET on this [zoom link](https://zoom.us/j/98835923979?pwd=RG5JZ3czZEtmRDlGdms0ZktmMFQvUT09). +We meet every other Thursday - 4p ET on this [zoom link](https://zoom.us/j/98835923979?pwd=RG5JZ3czZEtmRDlGdms0ZktmMFQvUT09). #### Agenda -You can see the [agenda and meeting notes here](https://docs.google.com/document/d/1b6d3CVJLsl7YnTE7ZaZQHdkdYIvuOQ8rzAmvVdypOWM/edit?usp=sharing). +You can see the [agenda and meeting notes here](https://docs.google.com/document/d/1b6d3CVJLsl7YnTE7ZaZQHdkdYIvuOQ8rzAmvVdypOWM/edit?usp=sharing). ## Stargazers over time diff --git a/checks/cii_best_practices.go b/checks/cii_best_practices.go index fd7f5679ff01..4f39c9c78e3f 100644 --- a/checks/cii_best_practices.go +++ b/checks/cii_best_practices.go @@ -19,6 +19,8 @@ import ( "github.com/ossf/scorecard/v4/checks/evaluation" "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" ) // CheckCIIBestPractices is the registered name for CIIBestPractices. @@ -40,11 +42,17 @@ func CIIBestPractices(c *checker.CheckRequest) checker.CheckResult { return checker.CreateRuntimeErrorResult(CheckCIIBestPractices, e) } - // Return raw results. - if c.RawResults != nil { - c.RawResults.CIIBestPracticesResults = rawData + // Set the raw results. + pRawResults := getRawResults(c) + pRawResults.CIIBestPracticesResults = rawData + + // Evaluate the probes. + findings, err := zrunner.Run(pRawResults, probes.CIIBestPractices) + if err != nil { + e := sce.WithMessage(sce.ErrScorecardInternal, err.Error()) + return checker.CreateRuntimeErrorResult(CheckCIIBestPractices, e) } // Return the score evaluation. - return evaluation.CIIBestPractices(CheckCIIBestPractices, c.Dlogger, &rawData) + return evaluation.CIIBestPractices(CheckCIIBestPractices, findings, c.Dlogger) } diff --git a/checks/evaluation/cii_best_practices.go b/checks/evaluation/cii_best_practices.go index fa940b4c0ba9..0dd8511cf383 100644 --- a/checks/evaluation/cii_best_practices.go +++ b/checks/evaluation/cii_best_practices.go @@ -15,14 +15,12 @@ package evaluation import ( - "fmt" - "github.com/ossf/scorecard/v4/checker" - "github.com/ossf/scorecard/v4/clients" sce "github.com/ossf/scorecard/v4/errors" + "github.com/ossf/scorecard/v4/finding" + "github.com/ossf/scorecard/v4/probes/hasOpenSSFBadge" ) -// Note: exported for unit tests. const ( silverScore = 7 // Note: if this value is changed, please update the action's threshold score @@ -32,31 +30,54 @@ const ( ) // CIIBestPractices applies the score policy for the CIIBestPractices check. -func CIIBestPractices(name string, _ checker.DetailLogger, r *checker.CIIBestPracticesData) checker.CheckResult { - if r == nil { - e := sce.WithMessage(sce.ErrScorecardInternal, "empty raw data") +func CIIBestPractices(name string, + findings []finding.Finding, dl checker.DetailLogger, +) checker.CheckResult { + expectedProbes := []string{ + hasOpenSSFBadge.Probe, + } + + if !finding.UniqueProbesEqual(findings, expectedProbes) { + e := sce.WithMessage(sce.ErrScorecardInternal, "invalid probe results") return checker.CreateRuntimeErrorResult(name, e) } - var results checker.CheckResult - switch r.Badge { - case clients.NotFound: - results = checker.CreateMinScoreResult(name, "no effort to earn an OpenSSF best practices badge detected") - case clients.InProgress: - msg := fmt.Sprintf("badge detected: %v", r.Badge) - results = checker.CreateResultWithScore(name, msg, inProgressScore) - case clients.Passing: - msg := fmt.Sprintf("badge detected: %v", r.Badge) - results = checker.CreateResultWithScore(name, msg, passingScore) - case clients.Silver: - msg := fmt.Sprintf("badge detected: %v", r.Badge) - results = checker.CreateResultWithScore(name, msg, silverScore) - case clients.Gold: - msg := fmt.Sprintf("badge detected: %v", r.Badge) - results = checker.CreateMaxScoreResult(name, msg) - case clients.Unknown: - e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("unsupported badge: %v", r.Badge)) - results = checker.CreateRuntimeErrorResult(name, e) + var score int + var text string + + if len(findings) != 1 { + errText := "invalid probe results: multiple findings detected" + e := sce.WithMessage(sce.ErrScorecardInternal, errText) + return checker.CreateRuntimeErrorResult(name, e) } - return results + + f := &findings[0] + if f.Outcome == finding.OutcomeNegative { + text = "no effort to earn an OpenSSF best practices badge detected" + return checker.CreateMinScoreResult(name, text) + } + //nolint:nestif + if _, hasKey := f.Values[hasOpenSSFBadge.GoldLevel]; hasKey { + score = checker.MaxResultScore + text = "badge detected: Gold" + } else if _, hasKey := f.Values[hasOpenSSFBadge.SilverLevel]; hasKey { + score = silverScore + text = "badge detected: Silver" + } else if _, hasKey := f.Values[hasOpenSSFBadge.PassingLevel]; hasKey { + score = passingScore + text = "badge detected: Passing" + } else if _, hasKey := f.Values[hasOpenSSFBadge.InProgressLevel]; hasKey { + score = inProgressScore + text = "badge detected: InProgress" + } else if _, hasKey := f.Values[hasOpenSSFBadge.UnknownLevel]; hasKey { + text = "unknown badge detected" + e := sce.WithMessage(sce.ErrScorecardInternal, text) + return checker.CreateRuntimeErrorResult(name, e) + } else { + text = "unsupported badge detected" + e := sce.WithMessage(sce.ErrScorecardInternal, text) + return checker.CreateRuntimeErrorResult(name, e) + } + + return checker.CreateResultWithScore(name, text, score) } diff --git a/checks/evaluation/cii_best_practices_test.go b/checks/evaluation/cii_best_practices_test.go index d1798bfa72b5..50b15ad1b5f6 100644 --- a/checks/evaluation/cii_best_practices_test.go +++ b/checks/evaluation/cii_best_practices_test.go @@ -16,71 +16,136 @@ package evaluation import ( "testing" - "github.com/ossf/scorecard/v4/checker" - "github.com/ossf/scorecard/v4/clients" + sce "github.com/ossf/scorecard/v4/errors" + "github.com/ossf/scorecard/v4/finding" + "github.com/ossf/scorecard/v4/probes/hasOpenSSFBadge" + scut "github.com/ossf/scorecard/v4/utests" ) func TestCIIBestPractices(t *testing.T) { - t.Run("CIIBestPractices", func(t *testing.T) { - t.Run("in progress", func(t *testing.T) { - r := &checker.CIIBestPracticesData{ - Badge: clients.InProgress, - } - result := CIIBestPractices("CIIBestPractices", nil, r) - if result.Score != inProgressScore { - t.Errorf("CIIBestPractices() = %v, want %v", result.Score, inProgressScore) - } - }) - t.Run("passing", func(t *testing.T) { - r := &checker.CIIBestPracticesData{ - Badge: clients.Passing, - } - result := CIIBestPractices("CIIBestPractices", nil, r) - if result.Score != passingScore { - t.Errorf("CIIBestPractices() = %v, want %v", result.Score, passingScore) - } - }) - t.Run("silver", func(t *testing.T) { - r := &checker.CIIBestPracticesData{ - Badge: clients.Silver, - } - result := CIIBestPractices("CIIBestPractices", nil, r) - if result.Score != silverScore { - t.Errorf("CIIBestPractices() = %v, want %v", result.Score, silverScore) - } - }) - t.Run("gold", func(t *testing.T) { - r := &checker.CIIBestPracticesData{ - Badge: clients.Gold, - } - result := CIIBestPractices("CIIBestPractices", nil, r) - if result.Score != checker.MaxResultScore { - t.Errorf("CIIBestPractices() = %v, want %v", result.Score, checker.MaxResultScore) - } - }) - t.Run("not found", func(t *testing.T) { - r := &checker.CIIBestPracticesData{ - Badge: clients.NotFound, - } - result := CIIBestPractices("CIIBestPractices", nil, r) - if result.Score != checker.MinResultScore { - t.Errorf("CIIBestPractices() = %v, want %v", result.Score, checker.MinResultScore) - } - }) - t.Run("error", func(t *testing.T) { - r := &checker.CIIBestPracticesData{ - Badge: clients.Unknown, - } - result := CIIBestPractices("CIIBestPractices", nil, r) - if result.Score != -1 { - t.Errorf("CIIBestPractices() = %v, want %v", result.Score, -1) - } - }) - t.Run("nil response", func(t *testing.T) { - result := CIIBestPractices("CIIBestPractices", nil, nil) - if result.Score != -1 { - t.Errorf("CIIBestPractices() = %v, want %v", result.Score, -1) + t.Parallel() + tests := []struct { + name string + findings []finding.Finding + result scut.TestReturn + }{ + { + name: "Unsupported badge found with negative finding", + findings: []finding.Finding{ + { + Probe: "hasOpenSSFBadge", + Outcome: finding.OutcomeNegative, + Values: map[string]int{ + "Unsupported": 1, + }, + }, + }, + result: scut.TestReturn{ + Score: 0, + }, + }, + { + name: "Unsupported badge found with positive finding", + findings: []finding.Finding{ + { + Probe: "hasOpenSSFBadge", + Outcome: finding.OutcomePositive, + Values: map[string]int{ + "Unsupported": 1, + }, + }, + }, + result: scut.TestReturn{ + Score: -1, + Error: sce.ErrScorecardInternal, + }, + }, + { + name: "Has InProgress Badge", + findings: []finding.Finding{ + { + Probe: "hasOpenSSFBadge", + Outcome: finding.OutcomePositive, + Values: map[string]int{ + hasOpenSSFBadge.InProgressLevel: 1, + }, + }, + }, + result: scut.TestReturn{ + Score: 2, + }, + }, + { + name: "Has Passing Badge", + findings: []finding.Finding{ + { + Probe: "hasOpenSSFBadge", + Outcome: finding.OutcomePositive, + Values: map[string]int{ + hasOpenSSFBadge.PassingLevel: 1, + }, + }, + }, + result: scut.TestReturn{ + Score: 5, + }, + }, + { + name: "Has Silver Badge", + findings: []finding.Finding{ + { + Probe: "hasOpenSSFBadge", + Outcome: finding.OutcomePositive, + Values: map[string]int{ + hasOpenSSFBadge.SilverLevel: 1, + }, + }, + }, + result: scut.TestReturn{ + Score: 7, + }, + }, + { + name: "Has Gold Badge", + findings: []finding.Finding{ + { + Probe: "hasOpenSSFBadge", + Outcome: finding.OutcomePositive, + Values: map[string]int{ + hasOpenSSFBadge.GoldLevel: 1, + }, + }, + }, + result: scut.TestReturn{ + Score: 10, + }, + }, + { + name: "Has Unknown Badge", + findings: []finding.Finding{ + { + Probe: "hasOpenSSFBadge", + Outcome: finding.OutcomePositive, + Values: map[string]int{ + "Unknown": 1, + }, + }, + }, + result: scut.TestReturn{ + Score: -1, + Error: sce.ErrScorecardInternal, + }, + }, + } + for _, tt := range tests { + tt := tt // Parallel testing + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + dl := scut.TestDetailLogger{} + got := CIIBestPractices(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/docs/beginner-checks.md b/docs/beginner-checks.md new file mode 100644 index 000000000000..dfca3765a53a --- /dev/null +++ b/docs/beginner-checks.md @@ -0,0 +1,76 @@ +# Getting Started with Scorecard Checks for Supply Chain Security + +Choosing which Scorecard checks to get started with as a project maintainer can be overwhelming. This page walks through some of the most important checks to start with for project improvement, focusing on the ones that give you the biggest payoff versus effort. They're broken down into three categories based on stages of the development process: [setting up your project](#1-setting-up-your-project), [accepting contributions from others](#2-manage-contributions-to-your-project), and [packaging the project to release to the world](#3-package-and-release-your-project). + +Note: not every Scorecard check or topic mentioned on this page might be relevant to your project. See below for more on [customizing your checks to your needs](#customize-your-checks-to-your-projects-needs). + +## 1. Setting up your project + +Start your project off strong by focusing on Scorecard checks that help you secure your project dependencies and workflows. + +- Secure your dependencies with the Vulnerabilities check and the Dependency-Update-Tool check +- Secure your workflows with the Token-Permissions check + +### The Vulnerabilities and Dependency-Update-Tool checks secure your dependencies + +Vulnerabilities are probably the most familiar security risk. By running Scorecard’s [Vulnerabilities check](https://github.com/ossf/scorecard/blob/main/docs/checks.md#vulnerabilities), you’ll get information about known vulnerabilities in your project, either through your codebase or through your direct and (for most languages) indirect dependencies. Tracking vulnerabilities in an indirect (sometimes called transitive) dependency can be tricky, but it’s important: [95% of vulnerable dependencies are transitive](https://www.endorlabs.com/state-of-dependency-management). + +If vulnerabilities are found in your dependencies, there are a few options: + +- Update the dependency to a non-vulnerable version (if available) +- Submit a patch to the vulnerable project +- Replace the dependency with a non-vulnerable dependency +- Remove the dependency and write code to take its place +- If you are sure a vulnerability does not impact your project, you may ignore the dependency by creating an [osv-scanner.toml](https://google.github.io/osv-scanner/configuration/#ignore-vulnerabilities-by-id) file in your project's root directory. + +If you have handled the vulnerabilities in your dependencies and are still not satisfied with your score for this check, make sure there are no open, unfixed vulnerabilities in your project’s own codebase. Once you have dealt with those, your score should improve. + +Next, Scorecard’s [Dependency-Update-Tool check](https://github.com/ossf/scorecard/blob/main/docs/checks.md#dependency-update-tool) encourages developers to keep their dependencies up to date, which is a great way to stay on top of security updates. This check awards a high score to a project if it uses a dependency update tool such as [Dependabot](https://docs.github.com/code-security/dependabot), [Renovate bot](https://docs.renovatebot.com/), or [PyUp](https://github.com/pyupio/pyup#readme). Using one of these tools helps streamline security processes by notifying you when vulnerabilities have surfaced in your dependencies or when new versions of your dependencies become available. + +Automated processes like these save you time and are highly configurable; for example, you can set your bot to update dependencies every day or every week at the same time. + +If you want to increase your score in this category, sign up for automatic updates with a dependency update tool. Keep in mind, though, that this check can only show that the dependency update tool is enabled, not that it is running. To benefit as much as possible from this check, be sure that you consistently run and act on the information from your dependency update tool. + +### Token-Permissions check helps you secure your workflows + +We suggest addressing the [Token-Permissions check](https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions) next because it takes just a few minutes to “set it and forget it” and secure your workflows. The check warns you when your project’s top-level tokens have `write` access instead of the more restrictive `read` access. Not all `write` access permissions need to be eliminated; some workflows may genuinely require them. But ensuring your top-level permissions have `read` access helps your project follow the principle of least privilege, which means that permissions are granted based on the minimal necessary access to perform a function. Projects that have top-level tokens set to `write` are granting more access than necessary to their automated workflow dependencies. If those dependencies were ever compromised, they could become an attack vector, and could be vulnerable to malicious code execution. + +To change the default setting for token permissions, add the following to the top of your workflow: + +``` +permissions: + contents: read +``` + +When you add a GitHub Action, be sure to read the Action’s docs to see if it needs any additional permissions; this information is usually prominent in the Action’s README. + +## 2. Manage contributions to your project + +As projects grow, they generally start including contributions from others. Contributors can expand your project’s scope and maturity, but they can also introduce security risk. To protect your project at this stage, we recommend improving the Branch Protection check, which allows you, the maintainer, to define rules that require certain workflows for certain branches. + +### Branch Protection reduces the risks of errors and hacks + +The [Branch Protection check](https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection) can help protect your code from unvetted changes. You can choose either or both of the following options: + +- Require code review. Select this if your project has more than one maintainer. Requiring review before changes are merged is one of the strongest protections you can give your code. This will also improve your [Code Review check](https://github.com/ossf/scorecard/blob/main/docs/checks.md#code-review) score. +- Require [status checks](https://docs.github.com/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks). All projects would benefit from selecting this option. It ensures that all Continuous Integration (CI) tests chosen by the maintainer have passed before a change is accepted, helping you catch mistakes early on in the development process. This will also improve your [CI Test check](https://github.com/ossf/scorecard/blob/main/docs/checks.md#ci-tests) score. + +## 3. Package and release your project + +Deciding how to securely share your code can be difficult. Building locally on your laptop may seem simpler at first, but using an automated build process to create your package on your CI/CD system provides you with security benefits that pay off in the long run. Scorecard’s Packaging check helps guide you through this process. + +### Packaging check verifies if a project is published as a package + +The [Packaging check](https://github.com/ossf/scorecard/blob/main/docs/checks.md#packaging) assesses whether a project has been published as a package. The check is currently limited to repositories hosted on GitHub. It looks for [GitHub packaging workflows](https://docs.github.com/packages/learn-github-packages/publishing-a-package) and language-specific GitHub Actions that upload the package to a corresponding hub, like npm or PyPI. + +Another benefit to releasing projects as packages is reproducibility—the version that new users can download and execute is identical to the one that you and other contributors have already reviewed. Packages also have clear versioning documentation that makes it easier to track whether any newly discovered security issues are applicable to your project. + +Packaging your projects makes it easier for users to receive security patches as updates. It also provides information about the release details to your users, which opens the door to more collaboration from your open-source peers. + +## Customize your checks to your project’s needs + +Based on the specifics of your project, not all the checks offered by Scorecard or discussed on this page may apply to you. For example, if you are the sole maintainer of an open-source project, the “Code Review” check would not be usable for your project. + +The languages you use also influence which checks will be useful to you. For example, if your project is built in C or C++, the Packaging and Dependency-Update-Tool checks will not be applicable because the C/C++ ecosystem does not have a centralized package manager. + +To learn more about all the checks Scorecard offers, see the [checks documentation](https://github.com/ossf/scorecard/blob/main/docs/checks.md#check-documentation). diff --git a/go.mod b/go.mod index 456eec206c45..7bedadc57301 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/google/go-github/v53 v53.2.0 github.com/google/osv-scanner v1.4.3 github.com/mcuadros/go-jsonschema-generator v0.0.0-20200330054847-ba7a369d4303 - github.com/onsi/ginkgo/v2 v2.13.1 + github.com/onsi/ginkgo/v2 v2.13.2 github.com/otiai10/copy v1.14.0 sigs.k8s.io/release-utils v0.6.0 ) @@ -106,7 +106,7 @@ require ( github.com/spdx/tools-golang v0.5.3 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect golang.org/x/mod v0.13.0 // indirect - golang.org/x/term v0.14.0 // indirect + golang.org/x/term v0.15.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/vuln v1.0.1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect @@ -172,12 +172,12 @@ require ( 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.15.0 // indirect + golang.org/x/crypto v0.16.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/net v0.18.0 // indirect - golang.org/x/oauth2 v0.14.0 + golang.org/x/net v0.19.0 // indirect + golang.org/x/oauth2 v0.15.0 golang.org/x/sync v0.4.0 // indirect - golang.org/x/sys v0.14.0 // indirect + golang.org/x/sys v0.15.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.149.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 3acf5f1c25e3..add4801a4c73 100644 --- a/go.sum +++ b/go.sum @@ -622,8 +622,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.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU= -github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= +github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= +github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= 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= @@ -832,8 +832,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y 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.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -917,15 +917,15 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0= -golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -998,8 +998,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1008,8 +1008,8 @@ golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= -golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/probes/entries.go b/probes/entries.go index f745fb7512c6..b5600ebe7e2e 100644 --- a/probes/entries.go +++ b/probes/entries.go @@ -36,6 +36,7 @@ import ( "github.com/ossf/scorecard/v4/probes/hasLicenseFile" "github.com/ossf/scorecard/v4/probes/hasLicenseFileAtTopDir" "github.com/ossf/scorecard/v4/probes/hasOSVVulnerabilities" + "github.com/ossf/scorecard/v4/probes/hasOpenSSFBadge" "github.com/ossf/scorecard/v4/probes/hasRecentCommits" "github.com/ossf/scorecard/v4/probes/issueActivityByProjectMember" "github.com/ossf/scorecard/v4/probes/notArchived" @@ -117,6 +118,9 @@ var ( issueActivityByProjectMember.Run, notCreatedRecently.Run, } + CIIBestPractices = []ProbeImpl{ + hasOpenSSFBadge.Run, + } ) //nolint:gochecknoinits diff --git a/probes/hasOpenSSFBadge/def.yml b/probes/hasOpenSSFBadge/def.yml new file mode 100644 index 000000000000..1b62af9d7575 --- /dev/null +++ b/probes/hasOpenSSFBadge/def.yml @@ -0,0 +1,29 @@ +# 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. + +id: hasOpenSSFBadge +short: This check determines whether the project has an OpenSSF (formerly CII) Best Practices Badge. +motivation: > + The OpenSSF Best Practices badge indicates whether or not the project uses a set of security-focused best development practices for open source software. +implementation: > + The probe checks the type of the badge from the raw results. +outcome: + - If the project has a badge, the probe returns one OutcomePositive (1). The finding includes an entry in the `Values` map with the key describing the badge level. + - If the project does not have a badge, the probe returns one OutcomeNegative (0). +remediation: + effort: High + text: + - Learn about best practices by following [the OpenSSF Best Practices Badge criteria](https://www.bestpractices.dev/en/criteria/0). + markdown: + - Learn about best practices by following [the OpenSSF Best Practices Badge criteria](https://www.bestpractices.dev/en/criteria/0). diff --git a/probes/hasOpenSSFBadge/impl.go b/probes/hasOpenSSFBadge/impl.go new file mode 100644 index 000000000000..009326da535c --- /dev/null +++ b/probes/hasOpenSSFBadge/impl.go @@ -0,0 +1,80 @@ +// 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. + +//nolint:stylecheck +package hasOpenSSFBadge + +import ( + "embed" + "fmt" + + "github.com/ossf/scorecard/v4/checker" + "github.com/ossf/scorecard/v4/clients" + "github.com/ossf/scorecard/v4/finding" + "github.com/ossf/scorecard/v4/probes/internal/utils/uerror" +) + +//go:embed *.yml +var fs embed.FS + +const ( + Probe = "hasOpenSSFBadge" + GoldLevel = "Gold" + SilverLevel = "Silver" + PassingLevel = "Passing" + InProgressLevel = "InProgress" + UnknownLevel = "Unknown" +) + +func Run(raw *checker.RawResults) ([]finding.Finding, string, error) { + if raw == nil { + return nil, "", fmt.Errorf("%w: raw", uerror.ErrNil) + } + + r := raw.CIIBestPracticesResults + var badgeLevel string + + switch r.Badge { + case clients.Gold: + badgeLevel = GoldLevel + case clients.Silver: + badgeLevel = SilverLevel + case clients.Passing: + badgeLevel = PassingLevel + case clients.InProgress: + badgeLevel = InProgressLevel + case clients.Unknown: + badgeLevel = UnknownLevel + default: + f, err := finding.NewWith(fs, Probe, + "Project does not have an OpenSSF badge", nil, + finding.OutcomeNegative) + if err != nil { + return nil, Probe, fmt.Errorf("create finding: %w", err) + } + return []finding.Finding{*f}, Probe, nil + } + + f, err := finding.NewWith(fs, Probe, + fmt.Sprintf("OpenSSF best practice badge found at %s level.", badgeLevel), + nil, finding.OutcomePositive) + if err != nil { + return nil, Probe, fmt.Errorf("create finding: %w", err) + } + + f = f.WithValues(map[string]int{ + badgeLevel: 1, + }) + return []finding.Finding{*f}, Probe, nil +} diff --git a/probes/hasOpenSSFBadge/impl_test.go b/probes/hasOpenSSFBadge/impl_test.go new file mode 100644 index 000000000000..d4311d4de2b9 --- /dev/null +++ b/probes/hasOpenSSFBadge/impl_test.go @@ -0,0 +1,121 @@ +// 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. + +//nolint:stylecheck +package hasOpenSSFBadge + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/ossf/scorecard/v4/checker" + "github.com/ossf/scorecard/v4/clients" + "github.com/ossf/scorecard/v4/finding" +) + +func Test_Run(t *testing.T) { + t.Parallel() + //nolint:govet + tests := []struct { + name string + raw *checker.RawResults + outcomes []finding.Outcome + err error + }{ + { + name: "Has Gold badge", + raw: &checker.RawResults{ + CIIBestPracticesResults: checker.CIIBestPracticesData{ + Badge: clients.Gold, + }, + }, + outcomes: []finding.Outcome{ + finding.OutcomePositive, + }, + }, + { + name: "Has Silver badge", + raw: &checker.RawResults{ + CIIBestPracticesResults: checker.CIIBestPracticesData{ + Badge: clients.Silver, + }, + }, + outcomes: []finding.Outcome{ + finding.OutcomePositive, + }, + }, + { + name: "Has Passing badge", + raw: &checker.RawResults{ + CIIBestPracticesResults: checker.CIIBestPracticesData{ + Badge: clients.Passing, + }, + }, + outcomes: []finding.Outcome{ + finding.OutcomePositive, + }, + }, + { + name: "Has InProgress badge", + raw: &checker.RawResults{ + CIIBestPracticesResults: checker.CIIBestPracticesData{ + Badge: clients.InProgress, + }, + }, + outcomes: []finding.Outcome{ + finding.OutcomePositive, + }, + }, + { + name: "Has Unknown badge", + raw: &checker.RawResults{ + CIIBestPracticesResults: checker.CIIBestPracticesData{ + Badge: clients.Unknown, + }, + }, + outcomes: []finding.Outcome{ + finding.OutcomePositive, + }, + }, + } + 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() + + findings, s, err := Run(tt.raw) + if !cmp.Equal(tt.err, err, cmpopts.EquateErrors()) { + t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(tt.err, err, cmpopts.EquateErrors())) + } + if err != nil { + return + } + if diff := cmp.Diff(Probe, s); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + if diff := cmp.Diff(len(tt.outcomes), len(findings)); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + for i := range tt.outcomes { + outcome := &tt.outcomes[i] + f := &findings[i] + if diff := cmp.Diff(*outcome, f.Outcome); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + } + }) + } +} diff --git a/probes/notArchived/def.yml b/probes/notArchived/def.yml index b5858c1388f5..6d856ea2c635 100644 --- a/probes/notArchived/def.yml +++ b/probes/notArchived/def.yml @@ -13,7 +13,7 @@ # limitations under the License. id: notArchived -short: Check that the project is archvied +short: Check that the project is archived motivation: > A project which is not active might not be patched, have its dependencies patched, or be actively tested and used. However, a lack of active maintenance is not necessarily always a problem. Some software, especially smaller utility functions, does not normally need to be maintained. For example, a library that determines if an integer is even would not normally need maintenance unless an underlying implementation language definition changed. A lack of active maintenance should signal that potential users should investigate further to judge the situation. implementation: > @@ -24,4 +24,4 @@ outcome: remediation: effort: High text: - - Non-collaborators, members or owners cannot affect the outcome of this probe. \ No newline at end of file + - Non-collaborators, members or owners cannot affect the outcome of this probe.