mirror of
https://github.com/ossf/scorecard.git
synced 2024-09-11 08:55:27 +03:00
✨ [experimental] Probe support for security policy check (#3241)
* update Signed-off-by: laurentsimon <laurentsimon@google.com> * update Signed-off-by: laurentsimon <laurentsimon@google.com> * update Signed-off-by: laurentsimon <laurentsimon@google.com> * update Signed-off-by: laurentsimon <laurentsimon@google.com> * update Signed-off-by: laurentsimon <laurentsimon@google.com> * update Signed-off-by: laurentsimon <laurentsimon@google.com> * update Signed-off-by: laurentsimon <laurentsimon@google.com> * update Signed-off-by: laurentsimon <laurentsimon@google.com> * update Signed-off-by: laurentsimon <laurentsimon@google.com> * update Signed-off-by: laurentsimon <laurentsimon@google.com> * update Signed-off-by: laurentsimon <laurentsimon@google.com> * update Signed-off-by: laurentsimon <laurentsimon@google.com> * update Signed-off-by: laurentsimon <laurentsimon@google.com> * fix unit tests Signed-off-by: laurentsimon <laurentsimon@google.com> * comments Signed-off-by: laurentsimon <laurentsimon@google.com> * compilation fix Signed-off-by: laurentsimon <laurentsimon@google.com> * missing file Signed-off-by: laurentsimon <laurentsimon@google.com> * missing file Signed-off-by: laurentsimon <laurentsimon@google.com> * update reason string Signed-off-by: laurentsimon <laurentsimon@google.com> * typo Signed-off-by: laurentsimon <laurentsimon@google.com> * fix unit tests Signed-off-by: laurentsimon <laurentsimon@google.com> * typo Signed-off-by: laurentsimon <laurentsimon@google.com> * unit tests and linnter Signed-off-by: laurentsimon <laurentsimon@google.com> * comments Signed-off-by: laurentsimon <laurentsimon@google.com> * comments Signed-off-by: laurentsimon <laurentsimon@google.com> * missing file Signed-off-by: laurentsimon <laurentsimon@google.com> * unit tests for probes Signed-off-by: laurentsimon <laurentsimon@google.com> * linter Signed-off-by: laurentsimon <laurentsimon@google.com> * revert FileSize change Signed-off-by: laurentsimon <laurentsimon@google.com> --------- Signed-off-by: laurentsimon <laurentsimon@google.com>
This commit is contained in:
parent
f30ff23d82
commit
a8b255a224
@ -44,13 +44,12 @@ func DependencyUpdateTool(c *checker.CheckRequest) checker.CheckResult {
|
||||
return checker.CreateRuntimeErrorResult(CheckDependencyUpdateTool, e)
|
||||
}
|
||||
|
||||
// Return raw results.
|
||||
if c.RawResults != nil {
|
||||
c.RawResults.DependencyUpdateToolResults = rawData
|
||||
}
|
||||
// Set the raw results.
|
||||
pRawResults := getRawResults(c)
|
||||
pRawResults.DependencyUpdateToolResults = rawData
|
||||
|
||||
// Evaluate the probes.
|
||||
findings, err := evaluateProbes(c, CheckDependencyUpdateTool, probes.DependencyToolUpdates)
|
||||
findings, err := evaluateProbes(c, pRawResults, probes.DependencyToolUpdates)
|
||||
if err != nil {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
return checker.CreateRuntimeErrorResult(CheckDependencyUpdateTool, e)
|
||||
|
@ -150,11 +150,9 @@ func TestDependencyUpdateTool(t *testing.T) {
|
||||
mockRepo.EXPECT().ListFiles(gomock.Any()).Return(tt.files, nil)
|
||||
mockRepo.EXPECT().SearchCommits(gomock.Any()).Return(tt.SearchCommits, nil).Times(tt.CallSearchCommits)
|
||||
dl := scut.TestDetailLogger{}
|
||||
raw := checker.RawResults{}
|
||||
c := &checker.CheckRequest{
|
||||
RepoClient: mockRepo,
|
||||
Dlogger: &dl,
|
||||
RawResults: &raw,
|
||||
}
|
||||
res := DependencyUpdateTool(c)
|
||||
|
||||
|
@ -16,13 +16,29 @@ package evaluation
|
||||
|
||||
import (
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
sce "github.com/ossf/scorecard/v4/errors"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/toolDependabotInstalled"
|
||||
"github.com/ossf/scorecard/v4/probes/toolPyUpInstalled"
|
||||
"github.com/ossf/scorecard/v4/probes/toolRenovateInstalled"
|
||||
"github.com/ossf/scorecard/v4/probes/toolSonatypeLiftInstalled"
|
||||
)
|
||||
|
||||
// DependencyUpdateTool applies the score policy for the Dependency-Update-Tool check.
|
||||
func DependencyUpdateTool(name string,
|
||||
findings []finding.Finding,
|
||||
) checker.CheckResult {
|
||||
expectedProbes := []string{
|
||||
toolDependabotInstalled.Probe,
|
||||
toolPyUpInstalled.Probe,
|
||||
toolRenovateInstalled.Probe,
|
||||
toolSonatypeLiftInstalled.Probe,
|
||||
}
|
||||
if !finding.UniqueProbesEqual(findings, expectedProbes) {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, "invalid probe results")
|
||||
return checker.CreateRuntimeErrorResult(name, e)
|
||||
}
|
||||
|
||||
for i := range findings {
|
||||
f := &findings[i]
|
||||
if f.Outcome == finding.OutcomePositive {
|
||||
|
@ -39,6 +39,18 @@ func TestDependencyUpdateTool(t *testing.T) {
|
||||
Probe: "toolDependabotInstalled",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "toolPyUpInstalled",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "toolRenovateInstalled",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "toolSonatypeLiftInstalled",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
want: checker.CheckResult{
|
||||
Score: 10,
|
||||
@ -47,10 +59,22 @@ func TestDependencyUpdateTool(t *testing.T) {
|
||||
{
|
||||
name: "renovate",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "toolDependabotInstalled",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "toolPyUpInstalled",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "toolRenovateInstalled",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "toolSonatypeLiftInstalled",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
want: checker.CheckResult{
|
||||
Score: 10,
|
||||
@ -59,10 +83,22 @@ func TestDependencyUpdateTool(t *testing.T) {
|
||||
{
|
||||
name: "pyup",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "toolDependabotInstalled",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "toolPyUpInstalled",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "toolRenovateInstalled",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "toolSonatypeLiftInstalled",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
want: checker.CheckResult{
|
||||
Score: 10,
|
||||
@ -72,7 +108,19 @@ func TestDependencyUpdateTool(t *testing.T) {
|
||||
name: "sonatype",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "toolSonatypeInstalled",
|
||||
Probe: "toolDependabotInstalled",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "toolPyUpInstalled",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "toolRenovateInstalled",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "toolSonatypeLiftInstalled",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
@ -96,7 +144,7 @@ func TestDependencyUpdateTool(t *testing.T) {
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "toolSonatypeInstalled",
|
||||
Probe: "toolSonatypeLiftInstalled",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
@ -107,8 +155,7 @@ func TestDependencyUpdateTool(t *testing.T) {
|
||||
{
|
||||
name: "empty tool list",
|
||||
want: checker.CheckResult{
|
||||
Score: 0,
|
||||
Error: nil,
|
||||
Score: -1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -18,139 +18,62 @@ import (
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
sce "github.com/ossf/scorecard/v4/errors"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/securityPolicyContainsLinks"
|
||||
"github.com/ossf/scorecard/v4/probes/securityPolicyContainsText"
|
||||
"github.com/ossf/scorecard/v4/probes/securityPolicyContainsVulnerabilityDisclosure"
|
||||
"github.com/ossf/scorecard/v4/probes/securityPolicyPresent"
|
||||
)
|
||||
|
||||
func scoreSecurityCriteria(f checker.File,
|
||||
info []checker.SecurityPolicyInformation,
|
||||
dl checker.DetailLogger,
|
||||
) int {
|
||||
var urls, emails, discvuls, linkedContentLen, score int
|
||||
|
||||
emails = countSecInfo(info, checker.SecurityPolicyInformationTypeEmail, true)
|
||||
urls = countSecInfo(info, checker.SecurityPolicyInformationTypeLink, true)
|
||||
discvuls = countSecInfo(info, checker.SecurityPolicyInformationTypeText, false)
|
||||
|
||||
for _, i := range findSecInfo(info, checker.SecurityPolicyInformationTypeEmail, true) {
|
||||
linkedContentLen += len(i.InformationValue.Match)
|
||||
}
|
||||
for _, i := range findSecInfo(info, checker.SecurityPolicyInformationTypeLink, true) {
|
||||
linkedContentLen += len(i.InformationValue.Match)
|
||||
}
|
||||
|
||||
msg := checker.LogMessage{
|
||||
Path: f.Path,
|
||||
Type: f.Type,
|
||||
Text: "",
|
||||
}
|
||||
|
||||
// #1: linked content found (email/http): score += 6
|
||||
if (urls + emails) > 0 {
|
||||
score += 6
|
||||
msg.Text = "Found linked content in security policy"
|
||||
dl.Info(&msg)
|
||||
} else {
|
||||
msg.Text = "no email or URL found in security policy"
|
||||
dl.Warn(&msg)
|
||||
}
|
||||
|
||||
// #2: more bytes than the sum of the length of all the linked content found: score += 3
|
||||
// rationale: there appears to be information and context around those links
|
||||
// no credit if there is just a link to a site or an email address (those given above)
|
||||
// the test here is that each piece of linked content will likely contain a space
|
||||
// before and after the content (hence the two multiplier)
|
||||
if f.FileSize > 1 && (f.FileSize > uint(linkedContentLen+((urls+emails)*2))) {
|
||||
score += 3
|
||||
msg.Text = "Found text in security policy"
|
||||
dl.Info(&msg)
|
||||
} else {
|
||||
msg.Text = "No text (beyond any linked content) found in security policy"
|
||||
dl.Warn(&msg)
|
||||
}
|
||||
|
||||
// #3: found whole number(s) and or match(es) to "Disclos" and or "Vuln": score += 1
|
||||
// rationale: works towards the intent of the security policy file
|
||||
// regarding whom to contact about vuls and disclosures and timing
|
||||
// e.g., we'll disclose, report a vulnerability, 30 days, etc.
|
||||
// looking for at least 2 hits
|
||||
if discvuls > 1 {
|
||||
score += 1
|
||||
msg.Text = "Found disclosure, vulnerability, and/or timelines in security policy"
|
||||
dl.Info(&msg)
|
||||
} else {
|
||||
msg.Text = "One or no descriptive hints of disclosure, vulnerability, and/or timelines in security policy"
|
||||
dl.Warn(&msg)
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
func countSecInfo(secInfo []checker.SecurityPolicyInformation,
|
||||
infoType checker.SecurityPolicyInformationType,
|
||||
unique bool,
|
||||
) int {
|
||||
keys := make(map[string]bool)
|
||||
count := 0
|
||||
for _, entry := range secInfo {
|
||||
if _, present := keys[entry.InformationValue.Match]; !present && entry.InformationType == infoType {
|
||||
keys[entry.InformationValue.Match] = true
|
||||
count += 1
|
||||
} else if !unique && entry.InformationType == infoType {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func findSecInfo(secInfo []checker.SecurityPolicyInformation,
|
||||
infoType checker.SecurityPolicyInformationType,
|
||||
unique bool,
|
||||
) []checker.SecurityPolicyInformation {
|
||||
keys := make(map[string]bool)
|
||||
var secList []checker.SecurityPolicyInformation
|
||||
for _, entry := range secInfo {
|
||||
if _, present := keys[entry.InformationValue.Match]; !present && entry.InformationType == infoType {
|
||||
keys[entry.InformationValue.Match] = true
|
||||
secList = append(secList, entry)
|
||||
} else if !unique && entry.InformationType == infoType {
|
||||
secList = append(secList, entry)
|
||||
}
|
||||
}
|
||||
return secList
|
||||
}
|
||||
|
||||
// SecurityPolicy applies the score policy for the Security-Policy check.
|
||||
func SecurityPolicy(name string, dl checker.DetailLogger, r *checker.SecurityPolicyData) checker.CheckResult {
|
||||
if r == nil {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, "empty raw data")
|
||||
func SecurityPolicy(name string, findings []finding.Finding) checker.CheckResult {
|
||||
// We have 4 unique probes, each should have a finding.
|
||||
expectedProbes := []string{
|
||||
securityPolicyContainsVulnerabilityDisclosure.Probe,
|
||||
securityPolicyContainsLinks.Probe,
|
||||
securityPolicyContainsText.Probe,
|
||||
securityPolicyPresent.Probe,
|
||||
}
|
||||
if !finding.UniqueProbesEqual(findings, expectedProbes) {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, "invalid probe results")
|
||||
return checker.CreateRuntimeErrorResult(name, e)
|
||||
}
|
||||
|
||||
// Apply the policy evaluation.
|
||||
if len(r.PolicyFiles) == 0 {
|
||||
// If the file is unset, directly return as not detected.
|
||||
return checker.CreateMinScoreResult(name, "security policy file not detected")
|
||||
}
|
||||
|
||||
// TODO: although this a loop, the raw checks will only return one security policy
|
||||
// when more than one security policy file can be aggregated into a composite
|
||||
// score, that logic can be comprehended here.
|
||||
score := 0
|
||||
for _, spd := range r.PolicyFiles {
|
||||
score = scoreSecurityCriteria(spd.File,
|
||||
spd.Information, dl)
|
||||
|
||||
msg := checker.LogMessage{
|
||||
Path: spd.File.Path,
|
||||
Type: spd.File.Type,
|
||||
m := make(map[string]bool)
|
||||
for i := range findings {
|
||||
f := &findings[i]
|
||||
if f.Outcome == finding.OutcomePositive {
|
||||
switch f.Probe {
|
||||
case securityPolicyContainsVulnerabilityDisclosure.Probe:
|
||||
score += scoreProbeOnce(f.Probe, m, 1)
|
||||
case securityPolicyContainsLinks.Probe:
|
||||
score += scoreProbeOnce(f.Probe, m, 6)
|
||||
case securityPolicyContainsText.Probe:
|
||||
score += scoreProbeOnce(f.Probe, m, 3)
|
||||
case securityPolicyPresent.Probe:
|
||||
m[f.Probe] = true
|
||||
default:
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, "unknown probe results")
|
||||
return checker.CreateRuntimeErrorResult(name, e)
|
||||
}
|
||||
if msg.Type == finding.FileTypeURL {
|
||||
msg.Text = "security policy detected in org repo"
|
||||
} else {
|
||||
msg.Text = "security policy detected in current repo"
|
||||
}
|
||||
|
||||
dl.Info(&msg)
|
||||
}
|
||||
_, defined := m[securityPolicyPresent.Probe]
|
||||
if !defined {
|
||||
if score > 0 {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, "score calculation problem")
|
||||
return checker.CreateRuntimeErrorResult(name, e)
|
||||
}
|
||||
return checker.CreateMinScoreResult(name, "security policy file not detected")
|
||||
}
|
||||
|
||||
return checker.CreateResultWithScore(name, "security policy file detected", score)
|
||||
}
|
||||
|
||||
func scoreProbeOnce(probeID string, m map[string]bool, bump int) int {
|
||||
if _, exists := m[probeID]; !exists {
|
||||
m[probeID] = true
|
||||
return bump
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
@ -19,56 +19,83 @@ import (
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
scut "github.com/ossf/scorecard/v4/utests"
|
||||
)
|
||||
|
||||
func TestSecurityPolicy(t *testing.T) {
|
||||
t.Parallel()
|
||||
//nolint
|
||||
type args struct {
|
||||
name string
|
||||
r *checker.SecurityPolicyData
|
||||
}
|
||||
//nolint
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
findings []finding.Finding
|
||||
err bool
|
||||
want checker.CheckResult
|
||||
}{
|
||||
{
|
||||
name: "test_security_policy_1",
|
||||
args: args{
|
||||
name: "test_security_policy_1",
|
||||
name: "missing findings links",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "securityPolicyContainsVulnerabilityDisclosure",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyContainsText",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyPresent",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
want: checker.CheckResult{
|
||||
Score: -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test_security_policy_2",
|
||||
args: args{
|
||||
name: "test_security_policy_2",
|
||||
r: &checker.SecurityPolicyData{},
|
||||
name: "invalid probe name",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "securityPolicyContainsVulnerabilityDisclosure",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyContainsLinks",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyContainsText",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyPresent",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyInvalidProbeName",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
want: checker.CheckResult{
|
||||
Score: 0,
|
||||
Score: -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test_security_policy_3",
|
||||
args: args{
|
||||
name: "test_security_policy_3",
|
||||
r: &checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
name: "file found only",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "/etc/security/pam_env.conf",
|
||||
Type: finding.FileTypeURL,
|
||||
Probe: "securityPolicyContainsVulnerabilityDisclosure",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
Information: make([]checker.SecurityPolicyInformation, 0),
|
||||
{
|
||||
Probe: "securityPolicyContainsLinks",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyContainsText",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyPresent",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
want: checker.CheckResult{
|
||||
@ -76,22 +103,75 @@ func TestSecurityPolicy(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test_security_policy_4",
|
||||
args: args{
|
||||
name: "test_security_policy_4",
|
||||
r: &checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
name: "file not found with positive probes",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "/etc/security/pam_env.conf",
|
||||
Probe: "securityPolicyContainsVulnerabilityDisclosure",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
Information: make([]checker.SecurityPolicyInformation, 0),
|
||||
{
|
||||
Probe: "securityPolicyContainsLinks",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyContainsText",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyPresent",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
want: checker.CheckResult{
|
||||
Score: 0,
|
||||
Score: -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file found with no disclosure and text",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "securityPolicyContainsVulnerabilityDisclosure",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyContainsLinks",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyContainsText",
|
||||
Outcome: finding.OutcomeNegative,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyPresent",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
want: checker.CheckResult{
|
||||
Score: 6,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file found all positive",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "securityPolicyContainsVulnerabilityDisclosure",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyContainsLinks",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyContainsText",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
{
|
||||
Probe: "securityPolicyPresent",
|
||||
Outcome: finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
want: checker.CheckResult{
|
||||
Score: 10,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -100,9 +180,8 @@ func TestSecurityPolicy(t *testing.T) {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
x := checker.CheckRequest{Dlogger: &scut.TestDetailLogger{}}
|
||||
|
||||
got := SecurityPolicy(tt.args.name, x.Dlogger, tt.args.r)
|
||||
got := SecurityPolicy("SecurityPolicy", tt.findings)
|
||||
if tt.err {
|
||||
if got.Score != -1 {
|
||||
t.Errorf("SecurityPolicy() = %v, want %v", got, tt.want)
|
||||
@ -114,122 +193,3 @@ func TestSecurityPolicy(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestScoreSecurityCriteria(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct { //nolint:govet
|
||||
name string
|
||||
file checker.File
|
||||
info []checker.SecurityPolicyInformation
|
||||
expectedScore int
|
||||
}{
|
||||
{
|
||||
name: "Full score",
|
||||
file: checker.File{
|
||||
Path: "/path/to/security_policy.md",
|
||||
FileSize: 100,
|
||||
},
|
||||
info: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeEmail,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "security@example.com",
|
||||
LineNumber: 2,
|
||||
Offset: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeLink,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "https://example.com/report",
|
||||
LineNumber: 4,
|
||||
Offset: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeText,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "Disclose vulnerability",
|
||||
LineNumber: 6,
|
||||
Offset: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeText,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "30 days",
|
||||
LineNumber: 7,
|
||||
Offset: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedScore: 10,
|
||||
},
|
||||
{
|
||||
name: "Partial score",
|
||||
file: checker.File{
|
||||
Path: "/path/to/security_policy.md",
|
||||
FileSize: 50,
|
||||
},
|
||||
info: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeLink,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "https://example.com/report",
|
||||
LineNumber: 4,
|
||||
Offset: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeText,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "Disclose vulnerability",
|
||||
LineNumber: 6,
|
||||
Offset: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedScore: 9,
|
||||
},
|
||||
{
|
||||
name: "Low score",
|
||||
file: checker.File{
|
||||
Path: "/path/to/security_policy.md",
|
||||
FileSize: 10,
|
||||
},
|
||||
info: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeEmail,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "security@example.com",
|
||||
LineNumber: 2,
|
||||
Offset: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedScore: 6,
|
||||
},
|
||||
{
|
||||
name: "Low score",
|
||||
file: checker.File{
|
||||
Path: "/path/to/security_policy.md",
|
||||
FileSize: 5,
|
||||
},
|
||||
info: []checker.SecurityPolicyInformation{},
|
||||
expectedScore: 3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
mockDetailLogger := &scut.TestDetailLogger{}
|
||||
score := scoreSecurityCriteria(tc.file, tc.info, mockDetailLogger)
|
||||
|
||||
if score != tc.expectedScore {
|
||||
t.Errorf("scoreSecurityCriteria() mismatch, expected score: %d, got: %d", tc.expectedScore, score)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -24,11 +24,11 @@ import (
|
||||
)
|
||||
|
||||
// evaluateProbes runs the probes in probesToRun and logs its findings.
|
||||
func evaluateProbes(c *checker.CheckRequest, checkName string,
|
||||
func evaluateProbes(c *checker.CheckRequest, rawResults *checker.RawResults,
|
||||
probesToRun []probes.ProbeImpl,
|
||||
) ([]finding.Finding, error) {
|
||||
// Run the probes.
|
||||
findings, err := zrunner.Run(c.RawResults, probesToRun)
|
||||
findings, err := zrunner.Run(rawResults, probesToRun)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("zrunner.Run: %w", err)
|
||||
}
|
||||
@ -39,3 +39,12 @@ func evaluateProbes(c *checker.CheckRequest, checkName string,
|
||||
}
|
||||
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 {
|
||||
if c.RawResults != nil {
|
||||
return c.RawResults
|
||||
}
|
||||
return &checker.RawResults{}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ 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"
|
||||
)
|
||||
|
||||
// CheckSecurityPolicy is the registred name for SecurityPolicy.
|
||||
@ -44,9 +45,16 @@ func SecurityPolicy(c *checker.CheckRequest) checker.CheckResult {
|
||||
}
|
||||
|
||||
// Set the raw results.
|
||||
if c.RawResults != nil {
|
||||
c.RawResults.SecurityPolicyResults = rawData
|
||||
pRawResults := getRawResults(c)
|
||||
pRawResults.SecurityPolicyResults = rawData
|
||||
|
||||
// Evaluate the probes.
|
||||
findings, err := evaluateProbes(c, pRawResults, probes.SecurityPolicy)
|
||||
if err != nil {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
return checker.CreateRuntimeErrorResult(CheckSecurityPolicy, e)
|
||||
}
|
||||
|
||||
return evaluation.SecurityPolicy(CheckSecurityPolicy, c.Dlogger, &rawData)
|
||||
// Return the score evaluation.
|
||||
return evaluation.SecurityPolicy(CheckSecurityPolicy, findings)
|
||||
}
|
||||
|
@ -191,12 +191,12 @@ func TestSecurityPolicy(t *testing.T) {
|
||||
}).AnyTimes()
|
||||
|
||||
dl := scut.TestDetailLogger{}
|
||||
c := checker.CheckRequest{
|
||||
c := &checker.CheckRequest{
|
||||
RepoClient: mockRepo,
|
||||
Dlogger: &dl,
|
||||
}
|
||||
|
||||
res := SecurityPolicy(&c)
|
||||
res := SecurityPolicy(c)
|
||||
|
||||
if !scut.ValidateTestReturn(t, tt.name, &tt.want, &res, &dl) {
|
||||
t.Errorf("test failed: log message not present: %+v on %+v", tt.want, res)
|
||||
|
@ -39,13 +39,11 @@ var _ = Describe("E2E TEST:"+checks.CheckDependencyUpdateTool, func() {
|
||||
err = repoClient.InitRepo(repo, clients.HeadSHA, 0)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
raw := checker.RawResults{}
|
||||
req := checker.CheckRequest{
|
||||
Ctx: context.Background(),
|
||||
RepoClient: repoClient,
|
||||
Repo: repo,
|
||||
Dlogger: &dl,
|
||||
RawResults: &raw,
|
||||
}
|
||||
expected := scut.TestReturn{
|
||||
Error: nil,
|
||||
@ -68,13 +66,11 @@ var _ = Describe("E2E TEST:"+checks.CheckDependencyUpdateTool, func() {
|
||||
err = repoClient.InitRepo(repo, clients.HeadSHA, 0)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
raw := checker.RawResults{}
|
||||
req := checker.CheckRequest{
|
||||
Ctx: context.Background(),
|
||||
RepoClient: repoClient,
|
||||
Repo: repo,
|
||||
Dlogger: &dl,
|
||||
RawResults: &raw,
|
||||
}
|
||||
expected := scut.TestReturn{
|
||||
Error: nil,
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
@ -181,6 +182,24 @@ func (f *Finding) WithMessage(text string) *Finding {
|
||||
return f
|
||||
}
|
||||
|
||||
// UniqueProbesEqual checks the probe names present in a list of findings
|
||||
// and compare them against an expected list.
|
||||
func UniqueProbesEqual(findings []Finding, probes []string) bool {
|
||||
// Collect unique probes from findings.
|
||||
fm := make(map[string]bool)
|
||||
for i := range findings {
|
||||
f := &findings[i]
|
||||
fm[f.Probe] = true
|
||||
}
|
||||
// Collect probes from list.
|
||||
pm := make(map[string]bool)
|
||||
for i := range probes {
|
||||
p := &probes[i]
|
||||
pm[*p] = true
|
||||
}
|
||||
return reflect.DeepEqual(pm, fm)
|
||||
}
|
||||
|
||||
// WithLocation adds a location to an existing finding.
|
||||
// No copy is made.
|
||||
func (f *Finding) WithLocation(loc *Location) *Finding {
|
||||
|
@ -17,6 +17,10 @@ package probes
|
||||
import (
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/securityPolicyContainsLinks"
|
||||
"github.com/ossf/scorecard/v4/probes/securityPolicyContainsText"
|
||||
"github.com/ossf/scorecard/v4/probes/securityPolicyContainsVulnerabilityDisclosure"
|
||||
"github.com/ossf/scorecard/v4/probes/securityPolicyPresent"
|
||||
"github.com/ossf/scorecard/v4/probes/toolDependabotInstalled"
|
||||
"github.com/ossf/scorecard/v4/probes/toolPyUpInstalled"
|
||||
"github.com/ossf/scorecard/v4/probes/toolRenovateInstalled"
|
||||
@ -29,6 +33,14 @@ type ProbeImpl func(*checker.RawResults) ([]finding.Finding, string, error)
|
||||
var (
|
||||
// All represents all the probes.
|
||||
All []ProbeImpl
|
||||
// SecurityPolicy is all the probes for the
|
||||
// SecurityPolicy check.
|
||||
SecurityPolicy = []ProbeImpl{
|
||||
securityPolicyPresent.Run,
|
||||
securityPolicyContainsLinks.Run,
|
||||
securityPolicyContainsVulnerabilityDisclosure.Run,
|
||||
securityPolicyContainsText.Run,
|
||||
}
|
||||
// DependencyToolUpdates is all the probes for the
|
||||
// DpendencyUpdateTool check.
|
||||
DependencyToolUpdates = []ProbeImpl{
|
||||
@ -43,6 +55,7 @@ var (
|
||||
func init() {
|
||||
All = concatMultipleProbes([][]ProbeImpl{
|
||||
DependencyToolUpdates,
|
||||
SecurityPolicy,
|
||||
})
|
||||
}
|
||||
|
||||
|
21
probes/internal/utils/error.go
Normal file
21
probes/internal/utils/error.go
Normal file
@ -0,0 +1,21 @@
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var ErrNil = errors.New("nil pointer")
|
53
probes/internal/utils/secpolicy.go
Normal file
53
probes/internal/utils/secpolicy.go
Normal file
@ -0,0 +1,53 @@
|
||||
// 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 utils
|
||||
|
||||
import (
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
)
|
||||
|
||||
func CountSecInfo(secInfo []checker.SecurityPolicyInformation,
|
||||
infoType checker.SecurityPolicyInformationType,
|
||||
unique bool,
|
||||
) int {
|
||||
keys := make(map[string]bool)
|
||||
count := 0
|
||||
for _, entry := range secInfo {
|
||||
if _, present := keys[entry.InformationValue.Match]; !present && entry.InformationType == infoType {
|
||||
keys[entry.InformationValue.Match] = true
|
||||
count += 1
|
||||
} else if !unique && entry.InformationType == infoType {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func FindSecInfo(secInfo []checker.SecurityPolicyInformation,
|
||||
infoType checker.SecurityPolicyInformationType,
|
||||
unique bool,
|
||||
) []checker.SecurityPolicyInformation {
|
||||
keys := make(map[string]bool)
|
||||
var secList []checker.SecurityPolicyInformation
|
||||
for _, entry := range secInfo {
|
||||
if _, present := keys[entry.InformationValue.Match]; !present && entry.InformationType == infoType {
|
||||
keys[entry.InformationValue.Match] = true
|
||||
secList = append(secList, entry)
|
||||
} else if !unique && entry.InformationType == infoType {
|
||||
secList = append(secList, entry)
|
||||
}
|
||||
}
|
||||
return secList
|
||||
}
|
40
probes/securityPolicyContainsLinks/def.yml
Normal file
40
probes/securityPolicyContainsLinks/def.yml
Normal file
@ -0,0 +1,40 @@
|
||||
# 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: securityPolicyContainsLinks
|
||||
short: Check that the security policy contains web or email links.
|
||||
motivation: >
|
||||
URLs point users to additional information as well as online disclosure forms. Emails provide a point of contact for vulnerability disclosure.
|
||||
implementation: >
|
||||
The implementation looks for strings "http(s)://" to find URLs; and for strings "...@..." for email addresses.
|
||||
outcome:
|
||||
- If links are found, one finding with OutcomePositive (1) is returned for each file.
|
||||
- If no links are found, one finding with OutcomeNegative (0) is returned for each file.
|
||||
- If no file is found, one finding with OutcomeNegative (0) is returned.
|
||||
remediation:
|
||||
effort: Low
|
||||
text:
|
||||
- 'On GitHub:'
|
||||
- Enable private vulnerability disclosure in your repository settings https://docs.github.com/en/code-security/security-advisories/repository-security-advisories/configuring-private-vulnerability-reporting-for-a-repository
|
||||
- Add a section in your SECURITY.md indicating you have enabled private reporting, and tell them to follow the steps in https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability to report vulnerabilities.
|
||||
- 'On GitLab:'
|
||||
- Provide a point of contact in your SECURITY.md.
|
||||
- 'Examples: https://github.com/ossf/scorecard/blob/main/SECURITY.md, https://github.com/slsa-framework/slsa-github-generator/blob/main/SECURITY.md, https://github.com/sigstore/.github/blob/main/SECURITY.md.'
|
||||
markdown:
|
||||
- 'On GitHub:'
|
||||
- Enable private vulnerability disclosure in your [repository settings](https://docs.github.com/en/code-security/security-advisories/repository-security-advisories/configuring-private-vulnerability-reporting-for-a-repository)
|
||||
- Add a section in your SECURITY.md indicating you have enabled private reporting, and tell them to [follow these steps](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability to report vulnerabilities).
|
||||
- 'On GitLab:'
|
||||
- Provide a point of contact in your SECURITY.md.
|
||||
- 'Examples: [OpenSSF Scorecard](https://github.com/ossf/scorecard/blob/main/SECURITY.md), [SLSA builders](https://github.com/slsa-framework/slsa-github-generator/blob/main/SECURITY.md), [Sigstore](https://github.com/sigstore/.github/blob/main/SECURITY.md).'
|
68
probes/securityPolicyContainsLinks/impl.go
Normal file
68
probes/securityPolicyContainsLinks/impl.go
Normal file
@ -0,0 +1,68 @@
|
||||
// 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 securityPolicyContainsLinks
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
//go:embed *.yml
|
||||
var fs embed.FS
|
||||
|
||||
const Probe = "securityPolicyContainsLinks"
|
||||
|
||||
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
|
||||
if raw == nil {
|
||||
return nil, "", fmt.Errorf("%w: raw", utils.ErrNil)
|
||||
}
|
||||
var findings []finding.Finding
|
||||
policies := raw.SecurityPolicyResults.PolicyFiles
|
||||
for i := range policies {
|
||||
policy := &policies[i]
|
||||
emails := utils.CountSecInfo(policy.Information, checker.SecurityPolicyInformationTypeEmail, true)
|
||||
urls := utils.CountSecInfo(policy.Information, checker.SecurityPolicyInformationTypeLink, true)
|
||||
|
||||
if (urls + emails) > 0 {
|
||||
f, err := finding.NewPositive(fs, Probe,
|
||||
"Found linked content", policy.File.Location())
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
} else {
|
||||
f, err := finding.NewNegative(fs, Probe,
|
||||
"no linked content found", nil)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
}
|
||||
}
|
||||
|
||||
if len(findings) == 0 {
|
||||
f, err := finding.NewNegative(fs, Probe, "no security file to analyze", nil)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
}
|
||||
return findings, Probe, nil
|
||||
}
|
308
probes/securityPolicyContainsLinks/impl_test.go
Normal file
308
probes/securityPolicyContainsLinks/impl_test.go
Normal file
@ -0,0 +1,308 @@
|
||||
// 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 securityPolicyContainsLinks
|
||||
|
||||
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/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
// nolint:govet
|
||||
tests := []struct {
|
||||
name string
|
||||
raw *checker.RawResults
|
||||
outcomes []finding.Outcome
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "file present on repo no link",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on repo link",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeLink,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "https://www.bla.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on repo email",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeEmail,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "hey@google.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on org no link",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on org link",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeLink,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "https://www.bla.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on org email",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeEmail,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "hey@google.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files present on org and repo",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
},
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files present on org and repo email",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeEmail,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "hey@google.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files present on org and repo link",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeLink,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "https://www.bla.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file not present",
|
||||
raw: &checker.RawResults{},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil raw",
|
||||
err: utils.ErrNil,
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
40
probes/securityPolicyContainsText/def.yml
Normal file
40
probes/securityPolicyContainsText/def.yml
Normal file
@ -0,0 +1,40 @@
|
||||
# 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: securityPolicyContainsText
|
||||
short: Check that the security policy contains enough text and not just links.
|
||||
motivation: >
|
||||
Telling security researchers how to privately dislose problems with your project is important. The more details available, the better.
|
||||
implementation: >
|
||||
The implementation checks that the content of the SECURITY.md contains more than just a link or an email address. It does this by comparing the length of the content to the lengths of the links and email addresses.
|
||||
outcome:
|
||||
- If links are found, one finding with OutcomePositive (1) is returned for each file.
|
||||
- If no links are found, one finding with OutcomeNegative (0) is returned for each file.
|
||||
- If no file is found, one finding with OutcomeNegative (0) is returned.
|
||||
remediation:
|
||||
effort: Low
|
||||
text:
|
||||
- 'On GitHub:'
|
||||
- Enable private vulnerability disclosure in your repository settings https://docs.github.com/en/code-security/security-advisories/repository-security-advisories/configuring-private-vulnerability-reporting-for-a-repository
|
||||
- Add a section in your SECURITY.md indicating you have enabled private reporting, and tell them to follow the steps in https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability to report vulnerabilities.
|
||||
- 'On GitLab:'
|
||||
- Add a section in your SECURITY.md indicating the process to disclose vulnerabilities for your project.
|
||||
- 'Examples: https://github.com/ossf/scorecard/blob/main/SECURITY.md, https://github.com/slsa-framework/slsa-github-generator/blob/main/SECURITY.md, https://github.com/sigstore/.github/blob/main/SECURITY.md.'
|
||||
markdown:
|
||||
- 'On GitHub:'
|
||||
- Enable private vulnerability disclosure in your [repository settings](https://docs.github.com/en/code-security/security-advisories/repository-security-advisories/configuring-private-vulnerability-reporting-for-a-repository)
|
||||
- Add a section in your SECURITY.md indicating you have enabled private reporting, and tell them to [follow these steps](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability to report vulnerabilities).
|
||||
- 'On GitLab:'
|
||||
- Add a section in your SECURITY.md indicating the process to disclose vulnerabilities for your project.
|
||||
- 'Examples: [OpenSSF Scorecard](https://github.com/ossf/scorecard/blob/main/SECURITY.md), [SLSA builders](https://github.com/slsa-framework/slsa-github-generator/blob/main/SECURITY.md), [Sigstore](https://github.com/sigstore/.github/blob/main/SECURITY.md).'
|
75
probes/securityPolicyContainsText/impl.go
Normal file
75
probes/securityPolicyContainsText/impl.go
Normal file
@ -0,0 +1,75 @@
|
||||
// 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 securityPolicyContainsText
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
//go:embed *.yml
|
||||
var fs embed.FS
|
||||
|
||||
const Probe = "securityPolicyContainsText"
|
||||
|
||||
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
|
||||
if raw == nil {
|
||||
return nil, "", fmt.Errorf("%w: raw", utils.ErrNil)
|
||||
}
|
||||
var findings []finding.Finding
|
||||
policies := raw.SecurityPolicyResults.PolicyFiles
|
||||
for i := range policies {
|
||||
policy := &policies[i]
|
||||
linkedContentLen := 0
|
||||
emails := utils.CountSecInfo(policy.Information, checker.SecurityPolicyInformationTypeEmail, true)
|
||||
urls := utils.CountSecInfo(policy.Information, checker.SecurityPolicyInformationTypeLink, true)
|
||||
for _, i := range utils.FindSecInfo(policy.Information, checker.SecurityPolicyInformationTypeEmail, true) {
|
||||
linkedContentLen += len(i.InformationValue.Match)
|
||||
}
|
||||
for _, i := range utils.FindSecInfo(policy.Information, checker.SecurityPolicyInformationTypeLink, true) {
|
||||
linkedContentLen += len(i.InformationValue.Match)
|
||||
}
|
||||
|
||||
if policy.File.FileSize > 1 && (policy.File.FileSize > uint(linkedContentLen+((urls+emails)*2))) {
|
||||
f, err := finding.NewPositive(fs, Probe,
|
||||
"Found text in security policy", policy.File.Location())
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
} else {
|
||||
f, err := finding.NewNegative(fs, Probe,
|
||||
"No text (besides links / emails) found in security policy", nil)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
}
|
||||
}
|
||||
|
||||
if len(findings) == 0 {
|
||||
f, err := finding.NewNegative(fs, Probe, "no security file to analyze", nil)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
}
|
||||
return findings, Probe, nil
|
||||
}
|
414
probes/securityPolicyContainsText/impl_test.go
Normal file
414
probes/securityPolicyContainsText/impl_test.go
Normal file
@ -0,0 +1,414 @@
|
||||
// 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 securityPolicyContainsText
|
||||
|
||||
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/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
// nolint:govet
|
||||
tests := []struct {
|
||||
name string
|
||||
raw *checker.RawResults
|
||||
outcomes []finding.Outcome
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "file present on repo no text",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on repo links no text",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeEmail,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "myemail@google.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeLink,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "https://www.bla.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on repo links with short text",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
FileSize: 10,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeEmail,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "myemail@google.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeLink,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "https://www.bla.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on repo links with long text",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
FileSize: 50,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeEmail,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "myemail@google.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeLink,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "https://www.bla.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on repo no text",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on org links no text",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeEmail,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "myemail@google.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeLink,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "https://www.bla.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on org links with short text",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
FileSize: 10,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeEmail,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "myemail@google.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeLink,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "https://www.bla.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on org links with long text",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
FileSize: 50,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeEmail,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "myemail@google.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeLink,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "https://www.bla.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files present on org and repo no text",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
},
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files present on org and repo short text",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
FileSize: 10,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeEmail,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "myemail@google.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeLink,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "https://www.bla.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files present on org and repo long text",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
FileSize: 50,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeEmail,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "myemail@google.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeLink,
|
||||
InformationValue: checker.SecurityPolicyValueType{
|
||||
Match: "https://www.bla.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file not present",
|
||||
raw: &checker.RawResults{},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil raw",
|
||||
err: utils.ErrNil,
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
40
probes/securityPolicyContainsVulnerabilityDisclosure/def.yml
Normal file
40
probes/securityPolicyContainsVulnerabilityDisclosure/def.yml
Normal file
@ -0,0 +1,40 @@
|
||||
# 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: securityPolicyContainsVulnerabilityDisclosure
|
||||
short: Check that the security policy indicates a vulnerability disclosure process.
|
||||
motivation: >
|
||||
If someone finds a vulnerability in the project, it is important for them to be able to communicate it to the maintainers.
|
||||
implementation: >
|
||||
The implementation looks for strings "Disclos" and "Vuln".
|
||||
outcome:
|
||||
- If information about the disclosure process is found in a security policy file, the probe returns one finding with OutcomePositive (1) for each file.
|
||||
- If no information about the disclosure process is found, the probe returns one finding with OutcomeNegative (0) for each file.
|
||||
- if no file is present, the probe returns one finding with OutcomeNegative (0).
|
||||
remediation:
|
||||
effort: Low
|
||||
text:
|
||||
- 'On GitHub:'
|
||||
- Enable private vulnerability disclosure in your repository settings https://docs.github.com/en/code-security/security-advisories/repository-security-advisories/configuring-private-vulnerability-reporting-for-a-repository
|
||||
- Add a section in your SECURITY.md indicating you have enabled private reporting, and tell them to follow the steps in https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability to report vulnerabilities.
|
||||
- 'On GitLab:'
|
||||
- Add a section in your SECURITY.md indicating the process to disclose vulnerabilities for your project.
|
||||
- 'Examples: https://github.com/ossf/scorecard/blob/main/SECURITY.md, https://github.com/slsa-framework/slsa-github-generator/blob/main/SECURITY.md, https://github.com/sigstore/.github/blob/main/SECURITY.md.'
|
||||
markdown:
|
||||
- 'On GitHub:'
|
||||
- Enable private vulnerability disclosure in your [repository settings](https://docs.github.com/en/code-security/security-advisories/repository-security-advisories/configuring-private-vulnerability-reporting-for-a-repository)
|
||||
- Add a section in your SECURITY.md indicating you have enabled private reporting, and tell them to [follow these steps](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability to report vulnerabilities).
|
||||
- 'On GitLab:'
|
||||
- Add a section in your SECURITY.md indicating the process to disclose vulnerabilities for your project.
|
||||
- 'Examples: [OpenSSF Scorecard](https://github.com/ossf/scorecard/blob/main/SECURITY.md), [SLSA builders](https://github.com/slsa-framework/slsa-github-generator/blob/main/SECURITY.md), [Sigstore](https://github.com/sigstore/.github/blob/main/SECURITY.md).'
|
66
probes/securityPolicyContainsVulnerabilityDisclosure/impl.go
Normal file
66
probes/securityPolicyContainsVulnerabilityDisclosure/impl.go
Normal file
@ -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.
|
||||
|
||||
// nolint:stylecheck
|
||||
package securityPolicyContainsVulnerabilityDisclosure
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
//go:embed *.yml
|
||||
var fs embed.FS
|
||||
|
||||
const Probe = "securityPolicyContainsVulnerabilityDisclosure"
|
||||
|
||||
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
|
||||
if raw == nil {
|
||||
return nil, "", fmt.Errorf("%w: raw", utils.ErrNil)
|
||||
}
|
||||
var findings []finding.Finding
|
||||
policies := raw.SecurityPolicyResults.PolicyFiles
|
||||
for i := range policies {
|
||||
policy := &policies[i]
|
||||
discvuls := utils.CountSecInfo(policy.Information, checker.SecurityPolicyInformationTypeText, false)
|
||||
if discvuls > 1 {
|
||||
f, err := finding.NewPositive(fs, Probe,
|
||||
"Found disclosure, vulnerability, and/or timelines in security policy", policy.File.Location())
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
} else {
|
||||
f, err := finding.NewNegative(fs, Probe,
|
||||
"One or no descriptive hints of disclosure, vulnerability, and/or timelines in security policy", nil)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
}
|
||||
}
|
||||
|
||||
if len(findings) == 0 {
|
||||
f, err := finding.NewNegative(fs, Probe, "no security file to analyze", nil)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
}
|
||||
return findings, Probe, nil
|
||||
}
|
@ -0,0 +1,269 @@
|
||||
// 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 securityPolicyContainsVulnerabilityDisclosure
|
||||
|
||||
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/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
// nolint:govet
|
||||
tests := []struct {
|
||||
name string
|
||||
raw *checker.RawResults
|
||||
outcomes []finding.Outcome
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "file present on repo no vuln",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on repo 2 vuln",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeText,
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on repo 1 vuln",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on org no vuln",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on org 2 vuln",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeText,
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on org 1 vuln",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files present on org and repo",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
},
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files present on org and repo 2 vuln",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
Information: []checker.SecurityPolicyInformation{
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeText,
|
||||
},
|
||||
{
|
||||
InformationType: checker.SecurityPolicyInformationTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file not present",
|
||||
raw: &checker.RawResults{},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil raw",
|
||||
err: utils.ErrNil,
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
43
probes/securityPolicyPresent/def.yml
Normal file
43
probes/securityPolicyPresent/def.yml
Normal file
@ -0,0 +1,43 @@
|
||||
# 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: securityPolicyPresent
|
||||
short: Check if a security policy is defined in the repository or in the org's .github repository.
|
||||
motivation: >
|
||||
A security policy (typically a SECURITY.md file) can give users information about what constitutes a vulnerability and how to report one securely so that information about a bug is not publicly visible.
|
||||
If you have a large orgnization, having a unified security policy across all your repositories may simplify the vulnerability disclosure response.
|
||||
implementation: >
|
||||
The implementation looks for the presence of security policy files in the repository or in '<org>/.github' repository. See https://github.com/ossf/scorecard/blob/main/checks/raw/security_policy.go#L139 for a detailed list of filenames.
|
||||
outcome:
|
||||
- If a security policy file is found, one finding with OutcomePositive (1) is returned.
|
||||
- If no security file is found, one finding with OutcomeNegative (0) is returned.
|
||||
remediation:
|
||||
effort: Medium
|
||||
text:
|
||||
- 'On GitHub:'
|
||||
- Enable private vulnerability disclosure in your repository settings https://docs.github.com/en/code-security/security-advisories/repository-security-advisories/configuring-private-vulnerability-reporting-for-a-repository
|
||||
- Add a section in your SECURITY.md indicating you have enabled private reporting, and tell them to follow the steps in https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability to report vulnerabilities.
|
||||
- 'On GitLab:'
|
||||
- Add a section in your SECURITY.md indicating the process to disclose vulnerabilities for your project.
|
||||
- 'Examples: https://github.com/ossf/scorecard/blob/main/SECURITY.md, https://github.com/slsa-framework/slsa-github-generator/blob/main/SECURITY.md, https://github.com/sigstore/.github/blob/main/SECURITY.md.'
|
||||
- For additional information on vulnerability disclosure, see https://github.com/ossf/oss-vulnerability-guide/blob/main/maintainer-guide.md.
|
||||
markdown:
|
||||
- Write a short paragraph for your SECURITY.md to explain the process to disclose security vulnerability for your project.
|
||||
- 'On GitHub:'
|
||||
- Enable private vulnerability disclosure in your [repository settings](https://docs.github.com/en/code-security/security-advisories/repository-security-advisories/configuring-private-vulnerability-reporting-for-a-repository)
|
||||
- Add a section in your SECURITY.md indicating you have enabled private reporting, and tell them to [follow these steps](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability to report vulnerabilities).
|
||||
- 'On GitLab:'
|
||||
- Provide a point of contact in your SECURITY.md.
|
||||
- 'Examples: [OpenSSF Scorecard](https://github.com/ossf/scorecard/blob/main/SECURITY.md), [SLSA builders](https://github.com/slsa-framework/slsa-github-generator/blob/main/SECURITY.md), [Sigstore](https://github.com/sigstore/.github/blob/main/SECURITY.md).'
|
||||
- For additional information on vulnerability disclosure, see [OpenSSF's maintainer's guide](https://github.com/ossf/oss-vulnerability-guide/blob/main/maintainer-guide.md).
|
65
probes/securityPolicyPresent/impl.go
Normal file
65
probes/securityPolicyPresent/impl.go
Normal file
@ -0,0 +1,65 @@
|
||||
// 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 securityPolicyPresent
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
//go:embed *.yml
|
||||
var fs embed.FS
|
||||
|
||||
const Probe = "securityPolicyPresent"
|
||||
|
||||
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
|
||||
if raw == nil {
|
||||
return nil, "", fmt.Errorf("%w: raw", utils.ErrNil)
|
||||
}
|
||||
var files []checker.File
|
||||
for i := range raw.SecurityPolicyResults.PolicyFiles {
|
||||
files = append(files, raw.SecurityPolicyResults.PolicyFiles[i].File)
|
||||
}
|
||||
|
||||
var findings []finding.Finding
|
||||
for i := range files {
|
||||
file := &files[i]
|
||||
f, err := finding.NewWith(fs, Probe, "security policy file detected",
|
||||
file.Location(), finding.OutcomePositive)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
f = f.WithRemediationMetadata(raw.Metadata.Metadata)
|
||||
findings = append(findings, *f)
|
||||
}
|
||||
|
||||
// No file found.
|
||||
if len(findings) == 0 {
|
||||
f, err := finding.NewWith(fs, Probe, "no security policy file detected",
|
||||
nil, finding.OutcomeNegative)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
f = f.WithRemediationMetadata(raw.Metadata.Metadata)
|
||||
findings = append(findings, *f)
|
||||
}
|
||||
|
||||
return findings, Probe, nil
|
||||
}
|
138
probes/securityPolicyPresent/impl_test.go
Normal file
138
probes/securityPolicyPresent/impl_test.go
Normal file
@ -0,0 +1,138 @@
|
||||
// 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 securityPolicyPresent
|
||||
|
||||
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/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
// nolint:govet
|
||||
tests := []struct {
|
||||
name string
|
||||
raw *checker.RawResults
|
||||
outcomes []finding.Outcome
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "file present on repo",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file present on org",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files present on org and repo",
|
||||
raw: &checker.RawResults{
|
||||
SecurityPolicyResults: checker.SecurityPolicyData{
|
||||
PolicyFiles: []checker.SecurityPolicyFile{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
},
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SECURITY.md",
|
||||
Type: finding.FileTypeText,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "file not present",
|
||||
raw: &checker.RawResults{},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil raw",
|
||||
err: utils.ErrNil,
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -22,8 +22,8 @@ implementation: >
|
||||
the implementation checks whether commits are authored by Dependabot. If none of these succeed, Dependabot is not installed.
|
||||
NOTE: if the configuration files are found, the probe does not ensure that the Dependabot is run or that the Dependabot's pull requests are merged.
|
||||
outcome:
|
||||
- If dependendabot is installed, the probe returns OutcomePositive (1)
|
||||
- If dependendabot is not installed, the probe returns OutcomeNegative (0)
|
||||
- If dependendabot is installed, the probe returns OutcomePositive (1) for each configuration.
|
||||
- If dependendabot is not installed, the probe returns one OutcomeNegative (0).
|
||||
remediation:
|
||||
effort: Low
|
||||
text:
|
||||
|
@ -17,16 +17,17 @@ package toolDependabotInstalled
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/utils"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
//go:embed *.yml
|
||||
var fs embed.FS
|
||||
|
||||
const probe = "toolDependabotInstalled"
|
||||
const Probe = "toolDependabotInstalled"
|
||||
|
||||
type dependabot struct{}
|
||||
|
||||
@ -39,12 +40,15 @@ func (t dependabot) Matches(tool *checker.Tool) bool {
|
||||
}
|
||||
|
||||
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
|
||||
if raw == nil {
|
||||
return nil, "", fmt.Errorf("%w: raw", utils.ErrNil)
|
||||
}
|
||||
tools := raw.DependencyUpdateToolResults.Tools
|
||||
var matcher dependabot
|
||||
// Check whether Dependabot tool is installed on the repo,
|
||||
// and create the corresponding findings.
|
||||
//nolint:wrapcheck
|
||||
return utils.ToolsRun(tools, fs, probe,
|
||||
return utils.ToolsRun(tools, fs, Probe,
|
||||
// Tool found will generate a positive result.
|
||||
finding.OutcomePositive,
|
||||
// Tool not found will generate a negative result.
|
||||
|
126
probes/toolDependabotInstalled/impl_test.go
Normal file
126
probes/toolDependabotInstalled/impl_test.go
Normal file
@ -0,0 +1,126 @@
|
||||
// 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 toolDependabotInstalled
|
||||
|
||||
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/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
// nolint:govet
|
||||
tests := []struct {
|
||||
name string
|
||||
raw *checker.RawResults
|
||||
outcomes []finding.Outcome
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "tool present",
|
||||
raw: &checker.RawResults{
|
||||
DependencyUpdateToolResults: checker.DependencyUpdateToolData{
|
||||
Tools: []checker.Tool{
|
||||
{
|
||||
Name: "Dependabot",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple correct tools",
|
||||
raw: &checker.RawResults{
|
||||
DependencyUpdateToolResults: checker.DependencyUpdateToolData{
|
||||
Tools: []checker.Tool{
|
||||
{
|
||||
Name: "Dependabot",
|
||||
},
|
||||
{
|
||||
Name: "Dependabot",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "different tool name",
|
||||
raw: &checker.RawResults{
|
||||
DependencyUpdateToolResults: checker.DependencyUpdateToolData{
|
||||
Tools: []checker.Tool{
|
||||
{
|
||||
Name: "not-Dependabot",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty results",
|
||||
raw: &checker.RawResults{},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil raw",
|
||||
err: utils.ErrNil,
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -22,8 +22,8 @@ implementation: >
|
||||
If the file is not found, PyUp is not installed.
|
||||
NOTE: the implementation does not ensure that PyUp is run or that PyUp's pull requests are merged.
|
||||
outcome:
|
||||
- If PyUp is installed, the probe returns OutcomePositive (1)
|
||||
- If PyUp is not installed, the probe returns OutcomeNegative (0)
|
||||
- If PyUp is installed, the probe returns OutcomePositive (1) for each configuration.
|
||||
- If PyUp is not installed, the probe returns OutcomeNegative (0).
|
||||
remediation:
|
||||
effort: Low
|
||||
text:
|
||||
|
@ -17,16 +17,17 @@ package toolPyUpInstalled
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/utils"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
//go:embed *.yml
|
||||
var fs embed.FS
|
||||
|
||||
const probe = "toolPyUpInstalled"
|
||||
const Probe = "toolPyUpInstalled"
|
||||
|
||||
type pyup struct{}
|
||||
|
||||
@ -39,12 +40,15 @@ func (t pyup) Matches(tool *checker.Tool) bool {
|
||||
}
|
||||
|
||||
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
|
||||
if raw == nil {
|
||||
return nil, "", fmt.Errorf("%w: raw", utils.ErrNil)
|
||||
}
|
||||
tools := raw.DependencyUpdateToolResults.Tools
|
||||
var matcher pyup
|
||||
// Check whether PyUp tool is installed on the repo,
|
||||
// and create the corresponding findings.
|
||||
//nolint:wrapcheck
|
||||
return utils.ToolsRun(tools, fs, probe,
|
||||
return utils.ToolsRun(tools, fs, Probe,
|
||||
// Tool found will generate a positive result.
|
||||
finding.OutcomePositive,
|
||||
// Tool not found will generate a negative result.
|
||||
|
126
probes/toolPyUpInstalled/impl_test.go
Normal file
126
probes/toolPyUpInstalled/impl_test.go
Normal file
@ -0,0 +1,126 @@
|
||||
// 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 toolPyUpInstalled
|
||||
|
||||
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/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
// nolint:govet
|
||||
tests := []struct {
|
||||
name string
|
||||
raw *checker.RawResults
|
||||
outcomes []finding.Outcome
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "tool present",
|
||||
raw: &checker.RawResults{
|
||||
DependencyUpdateToolResults: checker.DependencyUpdateToolData{
|
||||
Tools: []checker.Tool{
|
||||
{
|
||||
Name: "PyUp",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple correct tools",
|
||||
raw: &checker.RawResults{
|
||||
DependencyUpdateToolResults: checker.DependencyUpdateToolData{
|
||||
Tools: []checker.Tool{
|
||||
{
|
||||
Name: "PyUp",
|
||||
},
|
||||
{
|
||||
Name: "PyUp",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "different tool name",
|
||||
raw: &checker.RawResults{
|
||||
DependencyUpdateToolResults: checker.DependencyUpdateToolData{
|
||||
Tools: []checker.Tool{
|
||||
{
|
||||
Name: "not-PyUp",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty results",
|
||||
raw: &checker.RawResults{},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil raw",
|
||||
err: utils.ErrNil,
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -22,8 +22,8 @@ implementation: >
|
||||
If none of these files are found, Renovate is not installed.
|
||||
NOTE: the implementation does not ensure that Renovate is run or that Renovate's pull requests are merged.
|
||||
outcome:
|
||||
- If Renovate is installed, the probe returns OutcomePositive (1)
|
||||
- If Renovate is not installed, the probe returns OutcomeNegative (0)
|
||||
- If Renovate is installed, the probe returns OutcomePositive (1) for each configuration.
|
||||
- If Renovate is not installed, the probe returns OutcomeNegative (0).
|
||||
remediation:
|
||||
effort: Low
|
||||
text:
|
||||
|
@ -17,16 +17,17 @@ package toolRenovateInstalled
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/utils"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
//go:embed *.yml
|
||||
var fs embed.FS
|
||||
|
||||
const probe = "toolRenovateInstalled"
|
||||
const Probe = "toolRenovateInstalled"
|
||||
|
||||
type renovate struct{}
|
||||
|
||||
@ -39,12 +40,15 @@ func (t renovate) Matches(tool *checker.Tool) bool {
|
||||
}
|
||||
|
||||
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
|
||||
if raw == nil {
|
||||
return nil, "", fmt.Errorf("%w: raw", utils.ErrNil)
|
||||
}
|
||||
tools := raw.DependencyUpdateToolResults.Tools
|
||||
var matcher renovate
|
||||
// Check whether Renovate tool is installed on the repo,
|
||||
// and create the corresponding findings.
|
||||
//nolint:wrapcheck
|
||||
return utils.ToolsRun(tools, fs, probe,
|
||||
return utils.ToolsRun(tools, fs, Probe,
|
||||
// Tool found will generate a positive result.
|
||||
finding.OutcomePositive,
|
||||
// Tool not found will generate a negative result.
|
||||
|
126
probes/toolRenovateInstalled/impl_test.go
Normal file
126
probes/toolRenovateInstalled/impl_test.go
Normal file
@ -0,0 +1,126 @@
|
||||
// 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 toolRenovateInstalled
|
||||
|
||||
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/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
// nolint:govet
|
||||
tests := []struct {
|
||||
name string
|
||||
raw *checker.RawResults
|
||||
outcomes []finding.Outcome
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "tool present",
|
||||
raw: &checker.RawResults{
|
||||
DependencyUpdateToolResults: checker.DependencyUpdateToolData{
|
||||
Tools: []checker.Tool{
|
||||
{
|
||||
Name: "RenovateBot",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple correct tools",
|
||||
raw: &checker.RawResults{
|
||||
DependencyUpdateToolResults: checker.DependencyUpdateToolData{
|
||||
Tools: []checker.Tool{
|
||||
{
|
||||
Name: "RenovateBot",
|
||||
},
|
||||
{
|
||||
Name: "RenovateBot",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "different tool name",
|
||||
raw: &checker.RawResults{
|
||||
DependencyUpdateToolResults: checker.DependencyUpdateToolData{
|
||||
Tools: []checker.Tool{
|
||||
{
|
||||
Name: "not-RenovateBot",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty results",
|
||||
raw: &checker.RawResults{},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil raw",
|
||||
err: utils.ErrNil,
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -22,8 +22,8 @@ implementation: >
|
||||
If none of these files are found, Sonatype Lyft is not installed.
|
||||
NOTE: the implementation does not ensure that Sonatype Lyft is run or that Sonatype Lyft's pull requests are merged.
|
||||
outcome:
|
||||
- If Sonatype Lyft is installed, the probe returns OutcomePositive (1)
|
||||
- If Sonatype Lyft is not installed, the probe returns OutcomeNegative (0)
|
||||
- If Sonatype Lyft is installed, the probe returns OutcomePositive (1) for each configuration.
|
||||
- If Sonatype Lyft is not installed, the probe returns OutcomeNegative (0).
|
||||
remediation:
|
||||
effort: Low
|
||||
text:
|
||||
|
@ -17,16 +17,17 @@ package toolSonatypeLiftInstalled
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/utils"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
//go:embed *.yml
|
||||
var fs embed.FS
|
||||
|
||||
const probe = "toolSonatypeLiftInstalled"
|
||||
const Probe = "toolSonatypeLiftInstalled"
|
||||
|
||||
type sonatypeLyft struct{}
|
||||
|
||||
@ -39,12 +40,15 @@ func (t sonatypeLyft) Matches(tool *checker.Tool) bool {
|
||||
}
|
||||
|
||||
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
|
||||
if raw == nil {
|
||||
return nil, "", fmt.Errorf("%w: raw", utils.ErrNil)
|
||||
}
|
||||
tools := raw.DependencyUpdateToolResults.Tools
|
||||
var matcher sonatypeLyft
|
||||
// Check whether Sona Lyft tool is installed on the repo,
|
||||
// and create the corresponding findings.
|
||||
//nolint:wrapcheck
|
||||
return utils.ToolsRun(tools, fs, probe,
|
||||
return utils.ToolsRun(tools, fs, Probe,
|
||||
// Tool found will generate a positive result.
|
||||
finding.OutcomePositive,
|
||||
// Tool not found will generate a negative result.
|
||||
|
126
probes/toolSonatypeLiftInstalled/impl_test.go
Normal file
126
probes/toolSonatypeLiftInstalled/impl_test.go
Normal file
@ -0,0 +1,126 @@
|
||||
// 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 toolSonatypeLiftInstalled
|
||||
|
||||
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/finding"
|
||||
"github.com/ossf/scorecard/v4/probes/internal/utils"
|
||||
)
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
// nolint:govet
|
||||
tests := []struct {
|
||||
name string
|
||||
raw *checker.RawResults
|
||||
outcomes []finding.Outcome
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "tool present",
|
||||
raw: &checker.RawResults{
|
||||
DependencyUpdateToolResults: checker.DependencyUpdateToolData{
|
||||
Tools: []checker.Tool{
|
||||
{
|
||||
Name: "Sonatype Lift",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple correct tools",
|
||||
raw: &checker.RawResults{
|
||||
DependencyUpdateToolResults: checker.DependencyUpdateToolData{
|
||||
Tools: []checker.Tool{
|
||||
{
|
||||
Name: "Sonatype Lift",
|
||||
},
|
||||
{
|
||||
Name: "Sonatype Lift",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomePositive,
|
||||
finding.OutcomePositive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "different tool name",
|
||||
raw: &checker.RawResults{
|
||||
DependencyUpdateToolResults: checker.DependencyUpdateToolData{
|
||||
Tools: []checker.Tool{
|
||||
{
|
||||
Name: "not-Sonatype Lift",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty results",
|
||||
raw: &checker.RawResults{},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeNegative,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil raw",
|
||||
err: utils.ErrNil,
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user