Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Pinned-Dependencies continues on error #3515

Merged
merged 21 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d98cff8
Continue on error detecting OS
pnacht Sep 25, 2023
2282d62
Add tests for error detecting OS
pnacht Sep 25, 2023
d70fe00
Add ElementError to identify elements that errored
pnacht Oct 20, 2023
4cb3b29
Add Incomplete field to PinningDependenciesData
pnacht Oct 20, 2023
43481dc
Register job steps that errored out
pnacht Oct 20, 2023
8231381
Add tests that incomplete steps are caught
pnacht Oct 20, 2023
a008a16
Add warnings to details about incomplete steps
pnacht Oct 20, 2023
9ec3fac
Add tests that incomplete steps generate warnings
pnacht Oct 20, 2023
a605690
Register shell files skipped due to parser errors
pnacht Oct 20, 2023
7955c8a
Add tests showing when parser errors affect analysis
pnacht Oct 20, 2023
aa4bae7
Incomplete results logged as Info, not Warn
pnacht Oct 24, 2023
2b9e3d2
Remove `Type` from logging of incomplete results
pnacht Oct 30, 2023
37d0c34
Update tests after rebase
pnacht Oct 30, 2023
4701708
Add Unwrap for ElementError, improve its docs
pnacht Oct 30, 2023
f6768ff
Add ElementError case to evaluation unit test
pnacht Oct 31, 2023
de292f2
Move ElementError to checker/raw_result
pnacht Oct 31, 2023
970a7f1
Use finding.Location for ElementError.Element
pnacht Oct 31, 2023
01f3fe2
Use an ElementError for script parser errors
pnacht Oct 31, 2023
74f9892
Replace .Incomplete []error with .ProcessingErrors []ElementError
pnacht Oct 31, 2023
f388437
Adopt from reviewer comments
pnacht Nov 8, 2023
5705652
Merge branch 'main' into pinned-deps-continue-on-error
spencerschrock Nov 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion checker/raw_result.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package checker

import (
"fmt"
"time"

"github.com/ossf/scorecard/v4/clients"
Expand Down Expand Up @@ -110,7 +111,8 @@ const (

// PinningDependenciesData represents pinned dependency data.
type PinningDependenciesData struct {
Dependencies []Dependency
Dependencies []Dependency
ProcessingErrors []ElementError // jobs or files with errors may have incomplete results
}

// Dependency represents a dependency.
Expand Down Expand Up @@ -402,3 +404,23 @@ func (f *File) Location() *finding.Location {

return loc
}

// ElementError allows us to identify the "element" that led to the given error.
// The "element" is the specific "code under analysis" that caused the error. It should
// describe what caused the error as precisely as possible.
//
// For example, if a shell parsing error occurs while parsing a Dockerfile `RUN` block
// or a GitHub workflow's `run:` step, the "element" should point to the Dockerfile
// lines or workflow job step that caused the failure, not just the file path.
type ElementError struct {
Err error
Element *finding.Location
pnacht marked this conversation as resolved.
Show resolved Hide resolved
}

func (e *ElementError) Error() string {
return fmt.Sprintf("%s: %v", e.Err, *e.Element)
}

func (e *ElementError) Unwrap() error {
return e.Err
}
18 changes: 16 additions & 2 deletions checks/evaluation/pinned_dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"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"
)
Expand Down Expand Up @@ -65,6 +66,15 @@ func PinningDependencies(name string, c *checker.CheckRequest,
//nolint:errcheck
remediationMetadata, _ := remediation.New(c)

for _, e := range r.ProcessingErrors {
dl.Info(&checker.LogMessage{
Finding: &finding.Finding{
Message: generateTextIncompleteResults(e),
Location: e.Element,
},
})
}

for i := range r.Dependencies {
rr := r.Dependencies[i]
if rr.Location == nil {
Expand Down Expand Up @@ -105,7 +115,7 @@ func PinningDependencies(name string, c *checker.CheckRequest,
Type: rr.Location.Type,
Offset: rr.Location.Offset,
EndOffset: rr.Location.EndOffset,
Text: generateText(&rr),
Text: generateTextUnpinned(&rr),
Snippet: rr.Location.Snippet,
Remediation: generateRemediation(remediationMetadata, &rr),
})
Expand Down Expand Up @@ -176,7 +186,7 @@ func updatePinningResults(rr *checker.Dependency,
pr[rr.Type] = p
}

func generateText(rr *checker.Dependency) string {
func generateTextUnpinned(rr *checker.Dependency) string {
if rr.Type == checker.DependencyUseTypeGHAction {
// Check if we are dealing with a GitHub action or a third-party one.
gitHubOwned := fileparser.IsGitHubOwnedAction(rr.Location.Snippet)
Expand All @@ -187,6 +197,10 @@ func generateText(rr *checker.Dependency) string {
return fmt.Sprintf("%s not pinned by hash", rr.Type)
}

func generateTextIncompleteResults(e checker.ElementError) string {
return fmt.Sprintf("Possibly incomplete results: %s", e.Err)
}

func generateOwnerToDisplay(gitHubOwned bool) string {
if gitHubOwned {
return fmt.Sprintf("GitHub-owned %s", checker.DependencyUseTypeGHAction)
Expand Down
41 changes: 36 additions & 5 deletions checks/evaluation/pinned_dependencies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"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"
)

Expand Down Expand Up @@ -239,9 +240,10 @@ func Test_PinningDependencies(t *testing.T) {
t.Parallel()

tests := []struct {
name string
dependencies []checker.Dependency
expected scut.TestReturn
name string
dependencies []checker.Dependency
processingErrors []checker.ElementError
expected scut.TestReturn
}{
{
name: "all dependencies pinned",
Expand Down Expand Up @@ -796,6 +798,34 @@ func Test_PinningDependencies(t *testing.T) {
NumberOfDebug: 0,
},
},
{
name: "Skipped objects and dependencies",
dependencies: []checker.Dependency{
{
Location: &checker.File{},
Type: checker.DependencyUseTypeNpmCommand,
Pinned: asBoolPointer(false),
},
{
Location: &checker.File{},
Type: checker.DependencyUseTypeNpmCommand,
Pinned: asBoolPointer(false),
},
},
processingErrors: []checker.ElementError{
{
Err: sce.ErrorJobOSParsing,
Element: &finding.Location{},
},
},
expected: scut.TestReturn{
Error: nil,
Score: 0,
NumberOfWarn: 2, // unpinned deps
NumberOfInfo: 3, // 1 for npm deps, 2 for processing errors
NumberOfDebug: 0,
},
},
}

for _, tt := range tests {
Expand All @@ -807,7 +837,8 @@ func Test_PinningDependencies(t *testing.T) {
c := checker.CheckRequest{Dlogger: &dl}
actual := PinningDependencies("checkname", &c,
&checker.PinningDependenciesData{
Dependencies: tt.dependencies,
Dependencies: tt.dependencies,
ProcessingErrors: tt.processingErrors,
})

if !scut.ValidateTestReturn(t, tt.name, &tt.expected, &actual, &dl) {
Expand Down Expand Up @@ -990,7 +1021,7 @@ func TestGenerateText(t *testing.T) {

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := generateText(tc.dependency)
result := generateTextUnpinned(tc.dependency)
if !cmp.Equal(tc.expectedText, result) {
t.Errorf("generateText mismatch (-want +got):\n%s", cmp.Diff(tc.expectedText, result))
}
Expand Down
11 changes: 9 additions & 2 deletions checks/fileparser/github_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,15 @@ func GetOSesForJob(job *actionlint.Job) ([]string, error) {
}

if len(jobOSes) == 0 {
return jobOSes, sce.WithMessage(sce.ErrScorecardInternal,
fmt.Sprintf("unable to determine OS for job: %v", GetJobName(job)))
// This error is caught by the caller, which is responsible for adding more
// precise location information
spencerschrock marked this conversation as resolved.
Show resolved Hide resolved
jobName := GetJobName(job)
return jobOSes, &checker.ElementError{
Element: &finding.Location{
Snippet: &jobName,
},
Err: sce.ErrorJobOSParsing,
}
}
return jobOSes, nil
}
Expand Down
19 changes: 19 additions & 0 deletions checks/raw/pinned_dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package raw

import (
"errors"
"fmt"
"reflect"
"regexp"
Expand Down Expand Up @@ -360,6 +361,7 @@ var validateGitHubWorkflowIsFreeOfInsecureDownloads fileparser.DoWhileTrueOnFile
jobName = fileparser.GetJobName(job)
}
taintedFiles := make(map[string]bool)

for _, step := range job.Steps {
step := step
if !fileparser.IsStepExecKind(step, actionlint.ExecKindRun) {
Expand All @@ -382,6 +384,23 @@ var validateGitHubWorkflowIsFreeOfInsecureDownloads fileparser.DoWhileTrueOnFile
// https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
shell, err := fileparser.GetShellForStep(step, job)
if err != nil {
var elementError *checker.ElementError
if errors.As(err, &elementError) {
// Add the workflow name and step ID to the element
lineStart := uint(step.Pos.Line)
elementError.Element = &finding.Location{
Path: pathfn,
Snippet: elementError.Element.Snippet,
LineStart: &lineStart,
Type: finding.FileTypeSource,
}

pdata.ProcessingErrors = append(pdata.ProcessingErrors, *elementError)

// continue instead of break because other `run` steps may declare
// a valid shell we can scan
continue
}
return false, err
}
// Skip unsupported shells. We don't support Windows shells or some Unix shells.
Expand Down
Loading
Loading