From f983480ba2f531eacee25b7a3f3a1f0e2fae7dbe Mon Sep 17 00:00:00 2001 From: Rex P <106129829+another-rex@users.noreply.github.com> Date: Tue, 13 Dec 2022 13:46:43 +1300 Subject: [PATCH] :warning: OSV scanner integration (#2509) * Improve OSV scanning integration (squashed) Signed-off-by: Rex P * Add support for grouping vulnerabilities and aliases Signed-off-by: Rex P * Updated documentation, spit vulnerability output to multiple warnings Signed-off-by: Rex P * Updated documentation, spit vulnerability output to multiple warnings Signed-off-by: Rex P * Add its own codebase into docs Signed-off-by: Rex P * Update scorecard test to not prevent known vulns Signed-off-by: Rex P Signed-off-by: Rex P Co-authored-by: laurentsimon <64505099+laurentsimon@users.noreply.github.com> --- .gitignore | 1 + checker/client.go | 2 +- checks/evaluation/vulnerabilities.go | 20 ++++-- checks/raw/vulnerabilities.go | 15 +++-- checks/raw/vulnerabilities_test.go | 8 ++- checks/vulnerabilities.go | 1 + checks/vulnerabilities_test.go | 8 ++- clients/githubrepo/client.go | 5 ++ clients/githubrepo/tarball.go | 11 ++++ clients/gitlabrepo/client.go | 4 ++ clients/localdir/client.go | 9 +++ clients/mockclients/repo_client.go | 15 +++++ clients/mockclients/vulnerabilities.go | 12 ++-- clients/osv.go | 90 ++++++++++++++------------ clients/osv_test.go | 48 ++++++++++++++ clients/repo_client.go | 3 + clients/vulnerabilities.go | 19 ++++-- docs/checks.md | 10 +-- docs/checks/internal/checks.yaml | 16 +++-- e2e/attestor_policy_test.go | 6 +- e2e/vulnerabilities_test.go | 32 +++++---- go.mod | 10 ++- go.sum | 21 +++++- 23 files changed, 260 insertions(+), 106 deletions(-) create mode 100644 clients/osv_test.go diff --git a/.gitignore b/.gitignore index d0ebaf89820..d67e51d87dc 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ e2e-coverage.out .vscode/ *.iml .idea +.history # tools bin diff --git a/checker/client.go b/checker/client.go index fba3c459951..b65173b9a27 100644 --- a/checker/client.go +++ b/checker/client.go @@ -45,7 +45,7 @@ func GetClients(ctx context.Context, repoURI, localURI string, logger *log.Logge localdir.CreateLocalDirClient(ctx, logger), /*repoClient*/ nil, /*ossFuzzClient*/ nil, /*ciiClient*/ - nil, /*vulnClient*/ + clients.DefaultVulnerabilitiesClient(), /*vulnClient*/ retErr } diff --git a/checks/evaluation/vulnerabilities.go b/checks/evaluation/vulnerabilities.go index 0c71d7a4d69..cecca7ba8c6 100644 --- a/checks/evaluation/vulnerabilities.go +++ b/checks/evaluation/vulnerabilities.go @@ -18,6 +18,8 @@ import ( "fmt" "strings" + "github.com/google/osv-scanner/pkg/grouper" + "github.com/ossf/scorecard/v4/checker" sce "github.com/ossf/scorecard/v4/errors" ) @@ -31,21 +33,25 @@ func Vulnerabilities(name string, dl checker.DetailLogger, return checker.CreateRuntimeErrorResult(name, e) } - score := checker.MaxResultScore - IDs := []string{} + aliasVulnerabilities := []grouper.IDAliases{} for _, vuln := range r.Vulnerabilities { - IDs = append(IDs, vuln.ID) - score-- + aliasVulnerabilities = append(aliasVulnerabilities, grouper.IDAliases(vuln)) } + IDs := grouper.Group(aliasVulnerabilities) + score := checker.MaxResultScore - len(IDs) + if score < checker.MinResultScore { score = checker.MinResultScore } if len(IDs) > 0 { - dl.Warn(&checker.LogMessage{ - Text: fmt.Sprintf("HEAD is vulnerable to %s", strings.Join(IDs, ", ")), - }) + for _, v := range IDs { + dl.Warn(&checker.LogMessage{ + Text: fmt.Sprintf("Project is vulnerable to: %s", strings.Join(v.IDs, " / ")), + }) + } + return checker.CreateResultWithScore(name, fmt.Sprintf("%v existing vulnerabilities detected", len(IDs)), score) } diff --git a/checks/raw/vulnerabilities.go b/checks/raw/vulnerabilities.go index 70f739565b0..28718692eae 100644 --- a/checks/raw/vulnerabilities.go +++ b/checks/raw/vulnerabilities.go @@ -23,18 +23,19 @@ import ( // Vulnerabilities retrieves the raw data for the Vulnerabilities check. func Vulnerabilities(c *checker.CheckRequest) (checker.VulnerabilitiesData, error) { + commitHash := "" commits, err := c.RepoClient.ListCommits() - if err != nil { - return checker.VulnerabilitiesData{}, fmt.Errorf("repoClient.ListCommits: %w", err) + if err == nil && len(commits) > 0 && !allOf(commits, hasEmptySHA) { + commitHash = commits[0].SHA } - if len(commits) < 1 || allOf(commits, hasEmptySHA) { - return checker.VulnerabilitiesData{}, nil + localPath, err := c.RepoClient.LocalPath() + if err != nil { + return checker.VulnerabilitiesData{}, fmt.Errorf("RepoClient.LocalPath: %w", err) } - - resp, err := c.VulnerabilitiesClient.HasUnfixedVulnerabilities(c.Ctx, commits[0].SHA) + resp, err := c.VulnerabilitiesClient.ListUnfixedVulnerabilities(c.Ctx, commitHash, localPath) if err != nil { - return checker.VulnerabilitiesData{}, fmt.Errorf("vulnerabilitiesClient.HasUnfixedVulnerabilities: %w", err) + return checker.VulnerabilitiesData{}, fmt.Errorf("vulnerabilitiesClient.ListUnfixedVulnerabilities: %w", err) } return checker.VulnerabilitiesData{ Vulnerabilities: resp.Vulnerabilities, diff --git a/checks/raw/vulnerabilities_test.go b/checks/raw/vulnerabilities_test.go index 77258783212..14297ecf046 100644 --- a/checks/raw/vulnerabilities_test.go +++ b/checks/raw/vulnerabilities_test.go @@ -85,9 +85,13 @@ func TestVulnerabilities(t *testing.T) { return []clients.Commit{{SHA: "test"}}, nil }).AnyTimes() + mockRepo.EXPECT().LocalPath().DoAndReturn(func() (string, error) { + return "test_path", nil + }).AnyTimes() + mockVulnClient := mockrepo.NewMockVulnerabilitiesClient(ctrl) - mockVulnClient.EXPECT().HasUnfixedVulnerabilities(context.TODO(), gomock.Any()).DoAndReturn( - func(ctx context.Context, repo string) (clients.VulnerabilitiesResponse, error) { + mockVulnClient.EXPECT().ListUnfixedVulnerabilities(context.TODO(), gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, commit string, localPath string) (clients.VulnerabilitiesResponse, error) { if tt.vulnsError { //nolint return clients.VulnerabilitiesResponse{}, errors.New("error") diff --git a/checks/vulnerabilities.go b/checks/vulnerabilities.go index 5f950dca488..d31c5b3d73a 100644 --- a/checks/vulnerabilities.go +++ b/checks/vulnerabilities.go @@ -28,6 +28,7 @@ const CheckVulnerabilities = "Vulnerabilities" func init() { supportedRequestTypes := []checker.RequestType{ checker.CommitBased, + checker.FileBased, } if err := registerCheck(CheckVulnerabilities, Vulnerabilities, supportedRequestTypes); err != nil { // this should never happen diff --git a/checks/vulnerabilities_test.go b/checks/vulnerabilities_test.go index 30379f44e43..2867ee4c9e7 100644 --- a/checks/vulnerabilities_test.go +++ b/checks/vulnerabilities_test.go @@ -55,9 +55,13 @@ func TestVulnerabilities(t *testing.T) { return []clients.Commit{{SHA: "test"}}, nil }).MinTimes(1) + mockRepo.EXPECT().LocalPath().DoAndReturn(func() (string, error) { + return "test_path", nil + }).AnyTimes() + mockVulnClient := mockrepo.NewMockVulnerabilitiesClient(ctrl) - mockVulnClient.EXPECT().HasUnfixedVulnerabilities(context.TODO(), gomock.Any()).DoAndReturn( - func(ctx context.Context, repo string) (clients.VulnerabilitiesResponse, error) { + mockVulnClient.EXPECT().ListUnfixedVulnerabilities(context.TODO(), gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, commit string, localPath string) (clients.VulnerabilitiesResponse, error) { return tt.expected, tt.err }).MinTimes(1) diff --git a/clients/githubrepo/client.go b/clients/githubrepo/client.go index 07aa2ed295a..9b3e14fbfd3 100644 --- a/clients/githubrepo/client.go +++ b/clients/githubrepo/client.go @@ -130,6 +130,11 @@ func (client *Client) URI() string { return fmt.Sprintf("github.com/%s/%s", client.repourl.owner, client.repourl.repo) } +// LocalPath implements RepoClient.LocalPath. +func (client *Client) LocalPath() (string, error) { + return client.tarball.getLocalPath() +} + // ListFiles implements RepoClient.ListFiles. func (client *Client) ListFiles(predicate func(string) (bool, error)) ([]string, error) { return client.tarball.listFiles(predicate) diff --git a/clients/githubrepo/tarball.go b/clients/githubrepo/tarball.go index e72ff69c305..5435a0a07bb 100644 --- a/clients/githubrepo/tarball.go +++ b/clients/githubrepo/tarball.go @@ -243,6 +243,17 @@ func (handler *tarballHandler) listFiles(predicate func(string) (bool, error)) ( return ret, nil } +func (handler *tarballHandler) getLocalPath() (string, error) { + if err := handler.setup(); err != nil { + return "", fmt.Errorf("error during tarballHandler.setup: %w", err) + } + absTempDir, err := filepath.Abs(handler.tempDir) + if err != nil { + return "", fmt.Errorf("error during filepath.Abs: %w", err) + } + return absTempDir, nil +} + func (handler *tarballHandler) getFileContent(filename string) ([]byte, error) { if err := handler.setup(); err != nil { return nil, fmt.Errorf("error during tarballHandler.setup: %w", err) diff --git a/clients/gitlabrepo/client.go b/clients/gitlabrepo/client.go index 8597afea30d..a64164c63c5 100644 --- a/clients/gitlabrepo/client.go +++ b/clients/gitlabrepo/client.go @@ -136,6 +136,10 @@ func (client *Client) URI() string { return fmt.Sprintf("%s/%s/%s", client.repourl.hostname, client.repourl.owner, client.repourl.projectID) } +func (client *Client) LocalPath() (string, error) { + return "", nil +} + func (client *Client) ListFiles(predicate func(string) (bool, error)) ([]string, error) { return nil, nil } diff --git a/clients/localdir/client.go b/clients/localdir/client.go index 092541f9673..e1e1ce398d9 100644 --- a/clients/localdir/client.go +++ b/clients/localdir/client.go @@ -142,6 +142,15 @@ func applyPredicate( return files, nil } +// LocalPath implements RepoClient.LocalPath. +func (client *localDirClient) LocalPath() (string, error) { + clientPath, err := filepath.Abs(client.path) + if err != nil { + return "", fmt.Errorf("error during filepath.Abs: %w", err) + } + return clientPath, nil +} + // ListFiles implements RepoClient.ListFiles. func (client *localDirClient) ListFiles(predicate func(string) (bool, error)) ([]string, error) { client.once.Do(func() { diff --git a/clients/mockclients/repo_client.go b/clients/mockclients/repo_client.go index 3c3d7f5d605..0bfdd5f1703 100644 --- a/clients/mockclients/repo_client.go +++ b/clients/mockclients/repo_client.go @@ -333,6 +333,21 @@ func (mr *MockRepoClientMockRecorder) ListWebhooks() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListWebhooks", reflect.TypeOf((*MockRepoClient)(nil).ListWebhooks)) } +// LocalPath mocks base method. +func (m *MockRepoClient) LocalPath() (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LocalPath") + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LocalPath indicates an expected call of LocalPath. +func (mr *MockRepoClientMockRecorder) LocalPath() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalPath", reflect.TypeOf((*MockRepoClient)(nil).LocalPath)) +} + // Search mocks base method. func (m *MockRepoClient) Search(request clients.SearchRequest) (clients.SearchResponse, error) { m.ctrl.T.Helper() diff --git a/clients/mockclients/vulnerabilities.go b/clients/mockclients/vulnerabilities.go index de94e4fa712..e9c0b4ca798 100644 --- a/clients/mockclients/vulnerabilities.go +++ b/clients/mockclients/vulnerabilities.go @@ -50,17 +50,17 @@ func (m *MockVulnerabilitiesClient) EXPECT() *MockVulnerabilitiesClientMockRecor return m.recorder } -// HasUnfixedVulnerabilities mocks base method. -func (m *MockVulnerabilitiesClient) HasUnfixedVulnerabilities(context context.Context, commit string) (clients.VulnerabilitiesResponse, error) { +// ListUnfixedVulnerabilities mocks base method. +func (m *MockVulnerabilitiesClient) ListUnfixedVulnerabilities(context context.Context, commit, localDir string) (clients.VulnerabilitiesResponse, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HasUnfixedVulnerabilities", context, commit) + ret := m.ctrl.Call(m, "ListUnfixedVulnerabilities", context, commit, localDir) ret0, _ := ret[0].(clients.VulnerabilitiesResponse) ret1, _ := ret[1].(error) return ret0, ret1 } -// HasUnfixedVulnerabilities indicates an expected call of HasUnfixedVulnerabilities. -func (mr *MockVulnerabilitiesClientMockRecorder) HasUnfixedVulnerabilities(context, commit interface{}) *gomock.Call { +// ListUnfixedVulnerabilities indicates an expected call of ListUnfixedVulnerabilities. +func (mr *MockVulnerabilitiesClientMockRecorder) ListUnfixedVulnerabilities(context, commit, localDir interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasUnfixedVulnerabilities", reflect.TypeOf((*MockVulnerabilitiesClient)(nil).HasUnfixedVulnerabilities), context, commit) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListUnfixedVulnerabilities", reflect.TypeOf((*MockVulnerabilitiesClient)(nil).ListUnfixedVulnerabilities), context, commit, localDir) } diff --git a/clients/osv.go b/clients/osv.go index 32b60b2ba8e..b234057813a 100644 --- a/clients/osv.go +++ b/clients/osv.go @@ -15,62 +15,68 @@ package clients import ( - "bytes" "context" - "encoding/json" - "net/http" + "fmt" - "github.com/ossf/scorecard/v4/errors" + "github.com/google/osv-scanner/pkg/osvscanner" ) var _ VulnerabilitiesClient = osvClient{} type osvClient struct{} -const osvQueryEndpoint = "https://api.osv.dev/v1/query" - -type osvQuery struct { - Commit string `json:"commit"` -} - -type osvResp struct { - Vulns []struct { - ID string `json:"id"` - } `json:"vulns"` -} - -// HasUnfixedVulnerabilities implements VulnerabilityClient.HasUnfixedVulnerabilities. -func (v osvClient) HasUnfixedVulnerabilities(ctx context.Context, commit string) (VulnerabilitiesResponse, error) { - query, err := json.Marshal(&osvQuery{ - Commit: commit, - }) - if err != nil { - return VulnerabilitiesResponse{}, errors.WithMessage(err, "failed to marshal query") +// ListUnfixedVulnerabilities implements VulnerabilityClient.ListUnfixedVulnerabilities. +func (v osvClient) ListUnfixedVulnerabilities( + ctx context.Context, + commit, + localPath string, +) (VulnerabilitiesResponse, error) { + directoryPaths := []string{} + if localPath != "" { + directoryPaths = append(directoryPaths, localPath) } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, osvQueryEndpoint, bytes.NewReader(query)) - if err != nil { - return VulnerabilitiesResponse{}, errors.WithMessage(err, "failed to create request") + gitCommits := []string{} + if commit != "" { + gitCommits = append(gitCommits, commit) } - - httpClient := &http.Client{} - resp, err := httpClient.Do(req) + res, err := osvscanner.DoScan(osvscanner.ScannerActions{ + DirectoryPaths: directoryPaths, + SkipGit: true, + Recursive: true, + GitCommits: gitCommits, + }, nil) // TODO: Do logging? if err != nil { - return VulnerabilitiesResponse{}, errors.WithMessage(err, "failed to send request") + return VulnerabilitiesResponse{}, fmt.Errorf("osvscanner.DoScan: %w", err) } - defer resp.Body.Close() - var osvresp osvResp - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&osvresp); err != nil { - return VulnerabilitiesResponse{}, errors.WithMessage(err, "failed to decode response") + response := VulnerabilitiesResponse{} + vulns := res.Flatten() + for i := range vulns { + response.Vulnerabilities = append(response.Vulnerabilities, Vulnerability{ + ID: vulns[i].Vulnerability.ID, + Aliases: vulns[i].Vulnerability.Aliases, + }) + // Remove duplicate vulnerability IDs for now as we don't report information + // on the source of each vulnerability yet, therefore having multiple identical + // vuln IDs might be confusing. + response.Vulnerabilities = removeDuplicate( + response.Vulnerabilities, + func(key Vulnerability) string { return key.ID }, + ) } + return response, nil +} - var ret VulnerabilitiesResponse - for _, vuln := range osvresp.Vulns { - ret.Vulnerabilities = append(ret.Vulnerabilities, Vulnerability{ - ID: vuln.ID, - }) +// RemoveDuplicate removes duplicate entries from a slice. +func removeDuplicate[T any, K comparable](sliceList []T, keyExtract func(T) K) []T { + allKeys := make(map[K]bool) + list := []T{} + for _, item := range sliceList { + key := keyExtract(item) + if _, value := allKeys[key]; !value { + allKeys[key] = true + list = append(list, item) + } } - return ret, nil + return list } diff --git a/clients/osv_test.go b/clients/osv_test.go new file mode 100644 index 00000000000..9784ffb23a4 --- /dev/null +++ b/clients/osv_test.go @@ -0,0 +1,48 @@ +// Copyright 2022 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 clients + +import ( + "reflect" + "testing" +) + +func TestRemoveDuplicate(t *testing.T) { + t.Parallel() + tests := []struct { + name string + keyExtract func(string) string + list []string + want []string + }{ + { + name: "Basic list with dup items", + list: []string{"A", "B", "C", "B"}, + want: []string{"A", "B", "C"}, + keyExtract: func(in string) string { + return in + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := removeDuplicate(tt.list, tt.keyExtract) + if !reflect.DeepEqual(tt.want, got) { + t.Errorf("got %v, want %v", got, tt.want) + } + }) + } +} diff --git a/clients/repo_client.go b/clients/repo_client.go index 62b8fd770a8..cc190a2f0a0 100644 --- a/clients/repo_client.go +++ b/clients/repo_client.go @@ -32,6 +32,9 @@ type RepoClient interface { URI() string IsArchived() (bool, error) ListFiles(predicate func(string) (bool, error)) ([]string, error) + // Returns an absolute path to the local repository + // in the format that matches the local OS + LocalPath() (string, error) GetFileContent(filename string) ([]byte, error) GetBranch(branch string) (*BranchRef, error) GetCreatedAt() (time.Time, error) diff --git a/clients/vulnerabilities.go b/clients/vulnerabilities.go index 61de35f668b..8051a8dabee 100644 --- a/clients/vulnerabilities.go +++ b/clients/vulnerabilities.go @@ -20,7 +20,16 @@ import ( // VulnerabilitiesClient checks for vulnerabilities in vuln DB. type VulnerabilitiesClient interface { - HasUnfixedVulnerabilities(context context.Context, commit string) (VulnerabilitiesResponse, error) + ListUnfixedVulnerabilities( + context context.Context, + commit string, + localDir string, + ) (VulnerabilitiesResponse, error) +} + +// DefaultVulnerabilitiesClient returns a new OSV Vulnerabilities client. +func DefaultVulnerabilitiesClient() VulnerabilitiesClient { + return osvClient{} } // VulnerabilitiesResponse is the response from the vuln DB. @@ -30,10 +39,6 @@ type VulnerabilitiesResponse struct { // Vulnerability uniquely identifies a reported security vuln. type Vulnerability struct { - ID string -} - -// DefaultVulnerabilitiesClient returns a new OSV Vulnerabilities client. -func DefaultVulnerabilitiesClient() VulnerabilitiesClient { - return osvClient{} + ID string + Aliases []string } diff --git a/docs/checks.md b/docs/checks.md index 9a409a82d76..76ba275b10b 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -644,14 +644,16 @@ Additionally, points are reduced if certain write permissions are defined for a Risk: `High` (known vulnerabilities) -This check determines whether the project has open, unfixed vulnerabilities -using the [OSV (Open Source Vulnerabilities)](https://osv.dev/) service. An open -vulnerability is readily exploited by attackers and should be fixed as soon as +This check determines whether the project has open, unfixed vulnerabilities +in its own codebase or its dependencies using the [OSV (Open Source Vulnerabilities)](https://osv.dev/) service. +An open vulnerability is readily exploited by attackers and should be fixed as soon as possible. **Remediation steps** -- Fix the vulnerabilities. The details of each vulnerability can be found on . +- Fix the vulnerabilities in your own code base. The details of each vulnerability can be found on . +- If the vulnerability is in a dependency, update the dependency to a non-vulnerable version. If no update is available, consider whether to remove the dependency. +- If you believe the vulnerability does not affect your project, the vulnerability can be ignored. To ignore, create an `osv-scanner.toml` file next to the dependency manifest (e.g. package-lock.json) and specify the ID to ignore and reason. Details on the structure of `osv-scanner.toml` can be found on [OSV-Scanner repository](https://github.com/google/osv-scanner#ignore-vulnerabilities-by-id). ## Webhooks diff --git a/docs/checks/internal/checks.yaml b/docs/checks/internal/checks.yaml index b27aa60554a..63c0f97ee34 100644 --- a/docs/checks/internal/checks.yaml +++ b/docs/checks/internal/checks.yaml @@ -691,14 +691,22 @@ checks: description: | Risk: `High` (known vulnerabilities) - This check determines whether the project has open, unfixed vulnerabilities - using the [OSV (Open Source Vulnerabilities)](https://osv.dev/) service. An open - vulnerability is readily exploited by attackers and should be fixed as soon as + This check determines whether the project has open, unfixed vulnerabilities + in its own codebase or its dependencies using the [OSV (Open Source Vulnerabilities)](https://osv.dev/) service. + An open vulnerability is readily exploited by attackers and should be fixed as soon as possible. remediation: - >- - Fix the vulnerabilities. The details of each vulnerability can be found + Fix the vulnerabilities in your own code base. The details of each vulnerability can be found on . + - >- + If the vulnerability is in a dependency, update the dependency to a non-vulnerable version. If no update is available, consider whether to remove the dependency. + - >- + If you believe the vulnerability does not affect your project, the + vulnerability can be ignored. + To ignore, create an `osv-scanner.toml` file next to the dependency manifest (e.g. package-lock.json) and specify the ID to ignore and reason. + Details on the structure of `osv-scanner.toml` can be found on + [OSV-Scanner repository](https://github.com/google/osv-scanner#ignore-vulnerabilities-by-id). Dangerous-Workflow: risk: Critical diff --git a/e2e/attestor_policy_test.go b/e2e/attestor_policy_test.go index 839ba096b6d..7de6a87aa9d 100644 --- a/e2e/attestor_policy_test.go +++ b/e2e/attestor_policy_test.go @@ -141,7 +141,7 @@ var _ = Describe("E2E TEST PAT: scorecard-attestor policy", func() { commit: "fa0592fab28aa92560f04e1ae8649dfff566ae2b", policy: policy.AttestationPolicy{ PreventBinaryArtifacts: true, - PreventKnownVulnerabilities: true, + PreventKnownVulnerabilities: false, PreventUnpinnedDependencies: true, EnsureCodeReviewed: true, CodeReviewRequirements: policy.CodeReviewRequirements{ @@ -156,7 +156,7 @@ var _ = Describe("E2E TEST PAT: scorecard-attestor policy", func() { commit: "fa0592fab28aa92560f04e1ae8649dfff566ae2b", policy: policy.AttestationPolicy{ PreventBinaryArtifacts: true, - PreventKnownVulnerabilities: true, + PreventKnownVulnerabilities: false, PreventUnpinnedDependencies: true, EnsureCodeReviewed: true, CodeReviewRequirements: policy.CodeReviewRequirements{ @@ -172,7 +172,7 @@ var _ = Describe("E2E TEST PAT: scorecard-attestor policy", func() { commit: "fa0592fab28aa92560f04e1ae8649dfff566ae2b", policy: policy.AttestationPolicy{ PreventBinaryArtifacts: true, - PreventKnownVulnerabilities: true, + PreventKnownVulnerabilities: false, PreventUnpinnedDependencies: true, EnsureCodeReviewed: true, CodeReviewRequirements: policy.CodeReviewRequirements{ diff --git a/e2e/vulnerabilities_test.go b/e2e/vulnerabilities_test.go index 31de6d4110d..ad145706e75 100644 --- a/e2e/vulnerabilities_test.go +++ b/e2e/vulnerabilities_test.go @@ -29,15 +29,15 @@ import ( var _ = Describe("E2E TEST:"+checks.CheckVulnerabilities, func() { Context("E2E TEST:Validating vulnerabilities status", func() { - It("Should return that there are no vulnerabilities", func() { - repo, err := githubrepo.MakeGithubRepo("ossf/scorecard") + It("Should return that there are vulnerabilities", func() { + repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-vulnerabilities-open62541") Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) err = repoClient.InitRepo(repo, clients.HeadSHA, 0) Expect(err).Should(BeNil()) dl := scut.TestDetailLogger{} - req := checker.CheckRequest{ + checkRequest := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, VulnerabilitiesClient: clients.DefaultVulnerabilitiesClient(), @@ -46,23 +46,21 @@ var _ = Describe("E2E TEST:"+checks.CheckVulnerabilities, func() { } expected := scut.TestReturn{ Error: nil, - Score: checker.MaxResultScore, - NumberOfWarn: 0, + Score: checker.MaxResultScore - 3, // 3 vulnerabilities remove 3 points. + NumberOfWarn: 3, NumberOfInfo: 0, NumberOfDebug: 0, } - - result := checks.Vulnerabilities(&req) + result := checks.Vulnerabilities(&checkRequest) // New version. - Expect(scut.ValidateTestReturn(nil, "no osv vulnerabilities", &expected, &result, &dl)).Should(BeTrue()) + Expect(scut.ValidateTestReturn(nil, "osv vulnerabilities", &expected, &result, &dl)).Should(BeTrue()) Expect(repoClient.Close()).Should(BeNil()) }) - - It("Should return that there are vulnerabilities", func() { + It("Should return that there are vulnerabilities at commit", func() { repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-vulnerabilities-open62541") Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err = repoClient.InitRepo(repo, clients.HeadSHA, 0) + err = repoClient.InitRepo(repo, "de6367caa31b59e2156f83b04c2f30611b7ac393", 0) Expect(err).Should(BeNil()) dl := scut.TestDetailLogger{} @@ -76,7 +74,7 @@ var _ = Describe("E2E TEST:"+checks.CheckVulnerabilities, func() { expected := scut.TestReturn{ Error: nil, Score: checker.MaxResultScore - 3, // 3 vulnerabilities remove 3 points. - NumberOfWarn: 1, + NumberOfWarn: 3, NumberOfInfo: 0, NumberOfDebug: 0, } @@ -85,11 +83,11 @@ var _ = Describe("E2E TEST:"+checks.CheckVulnerabilities, func() { Expect(scut.ValidateTestReturn(nil, "osv vulnerabilities", &expected, &result, &dl)).Should(BeTrue()) Expect(repoClient.Close()).Should(BeNil()) }) - It("Should return that there are vulnerabilities at commit", func() { - repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-vulnerabilities-open62541") + It("Should return that there are vulnerable packages", func() { + repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-osv-e2e") Expect(err).Should(BeNil()) repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger) - err = repoClient.InitRepo(repo, "de6367caa31b59e2156f83b04c2f30611b7ac393", 0) + err = repoClient.InitRepo(repo, "2a81bfbc691786d6b8226a4092cca4f1509c842d", 0) Expect(err).Should(BeNil()) dl := scut.TestDetailLogger{} @@ -102,8 +100,8 @@ var _ = Describe("E2E TEST:"+checks.CheckVulnerabilities, func() { } expected := scut.TestReturn{ Error: nil, - Score: checker.MaxResultScore - 3, // 3 vulnerabilities remove 3 points. - NumberOfWarn: 1, + Score: checker.MaxResultScore - 2, // 2 vulnerabilities remove 2 points. + NumberOfWarn: 2, NumberOfInfo: 0, NumberOfDebug: 0, } diff --git a/go.mod b/go.mod index e6edf3687c0..2b28258523b 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 github.com/caarlos0/env/v6 v6.10.0 github.com/gobwas/glob v0.2.3 + github.com/google/osv-scanner v0.0.0-20221212045131-8aef1778b823 github.com/mcuadros/go-jsonschema-generator v0.0.0-20200330054847-ba7a369d4303 github.com/onsi/ginkgo/v2 v2.5.1 sigs.k8s.io/release-utils v0.6.0 @@ -62,6 +63,8 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/BurntSushi/toml v1.2.0 // indirect + github.com/CycloneDX/cyclonedx-go v0.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect github.com/google/gofuzz v1.1.0 // indirect @@ -70,9 +73,13 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.1 // indirect github.com/hashicorp/golang-lru v0.5.3 // indirect + github.com/jedib0t/go-pretty/v6 v6.4.0 // indirect github.com/json-iterator/go v1.1.10 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/package-url/packageurl-go v0.1.0 // indirect + github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb // indirect + github.com/spdx/tools-golang v0.3.0 // indirect golang.org/x/mod v0.7.0 // indirect golang.org/x/term v0.2.0 // indirect golang.org/x/time v0.1.0 // indirect @@ -142,7 +149,7 @@ require ( 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.1.0 // indirect - golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 + golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 golang.org/x/net v0.2.0 // indirect golang.org/x/oauth2 v0.1.0 // indirect golang.org/x/sync v0.1.0 // indirect @@ -172,5 +179,4 @@ replace ( 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 8b403b1a343..93f58c87cf8 100644 --- a/go.sum +++ b/go.sum @@ -153,7 +153,11 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/BurntSushi/toml v1.2.0/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.0 h1:jNxp8hL7UpcvPDFXjY+Y1ibFtsW+e5zyF9QoSmhK/zg= +github.com/CycloneDX/cyclonedx-go v0.7.0/go.mod h1:W5Z9w8pTTL+t+yG3PCiFRGlr8PUlE0pGWzKSJbsyXkg= github.com/GoogleCloudPlatform/cloudsql-proxy v1.29.0/go.mod h1:spvB9eLJH9dutlbPSRmHvSXXHOwGRyeXh1jVdquA2G8= github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= @@ -239,6 +243,7 @@ github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4m github.com/bombsimon/logrusr/v2 v2.0.1/go.mod h1:ByVAX+vHdLGAfdroiMg6q0zgq2FODY2lc5YJvzmOJio= github.com/bradleyfalzon/ghinstallation/v2 v2.1.0 h1:5+NghM1Zred9Z078QEZtm28G/kfDfZN/92gkDlLwGVA= github.com/bradleyfalzon/ghinstallation/v2 v2.1.0/go.mod h1:Xg3xPRN5Mcq6GDqeUVhFbjEWMb4JHCyWEeeBGEYQoTU= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/caarlos0/env/v6 v6.10.0 h1:lA7sxiGArZ2KkiqpOQNf8ERBRWI+v8MWIH+eGjSN22I= github.com/caarlos0/env/v6 v6.10.0/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -492,6 +497,8 @@ github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIG github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/osv-scanner v0.0.0-20221212045131-8aef1778b823 h1:gmZSgbjnj2XPX2jz1VY8QAUFqiHrYJDajG72CODinuc= +github.com/google/osv-scanner v0.0.0-20221212045131-8aef1778b823/go.mod h1:/pyEWUK+MMC8/CxgHmZNjtCq0pts7KYc6bXJEA9UPNE= 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= @@ -628,6 +635,8 @@ github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 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.0 h1:YlI/2zYDrweA4MThiYMKtGRfT+2qZOO65ulej8GTcVI= +github.com/jedib0t/go-pretty/v6 v6.4.0/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -770,6 +779,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 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/package-url/packageurl-go v0.1.0 h1:efWBc98O/dBZRg1pw2xiDzovnlMjCa9NPnfaiBduh8I= +github.com/package-url/packageurl-go v0.1.0/go.mod h1:C/ApiuWpmbpni4DIOECf6WCjFUZV7O1Fx7VAzrZHgBw= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -778,6 +789,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -841,6 +853,10 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb h1:bLo8hvc8XFm9J47r690TUKBzcjSWdJDxmjXJZ+/f92U= +github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= +github.com/spdx/tools-golang v0.3.0 h1:rtm+DHk3aAt74Fh0Wgucb4pCxjXV8SqHCPEb2iBd30k= +github.com/spdx/tools-golang v0.3.0/go.mod h1:RO4Y3IFROJnz+43JKm1YOrbtgQNljW4gAPpA/sY2eqo= 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= @@ -870,6 +886,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -977,8 +994,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-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE= +golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 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=