Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions checker/raw_result.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@ type SASTWorkflow struct {
// for the Security-Policy check.
type SecurityPolicyData struct {
PolicyFiles []SecurityPolicyFile
// PrivateVulnerabilityReportingEnabled indicates whether the platform
// has private vulnerability reporting enabled for the repo.
// nil means "unknown / not applicable for this platform".
PrivateVulnerabilityReportingEnabled *bool
}

// BinaryArtifactData contains the raw results
Expand Down
26 changes: 25 additions & 1 deletion checks/evaluation/security_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,18 @@ import (
"github.com/ossf/scorecard/v5/probes/securityPolicyContainsLinks"
"github.com/ossf/scorecard/v5/probes/securityPolicyContainsText"
"github.com/ossf/scorecard/v5/probes/securityPolicyContainsVulnerabilityDisclosure"
"github.com/ossf/scorecard/v5/probes/securityPolicyEnablesPrivateReporting"
"github.com/ossf/scorecard/v5/probes/securityPolicyPresent"
)

// SecurityPolicy applies the score policy for the Security-Policy check.
func SecurityPolicy(name string, findings []finding.Finding, dl checker.DetailLogger) checker.CheckResult {
// We have 4 unique probes, each should have a finding.
// We have 5 unique probes, each should have a finding.
expectedProbes := []string{
securityPolicyContainsVulnerabilityDisclosure.Probe,
securityPolicyContainsLinks.Probe,
securityPolicyContainsText.Probe,
securityPolicyEnablesPrivateReporting.Probe,
securityPolicyPresent.Probe,
}
if !finding.UniqueProbesEqual(findings, expectedProbes) {
Expand All @@ -40,6 +42,8 @@ func SecurityPolicy(name string, findings []finding.Finding, dl checker.DetailLo

score := 0
m := make(map[string]bool)
pvrEnabled := false
pvrUnknown := true
var logLevel checker.DetailType
for i := range findings {
f := &findings[i]
Expand All @@ -54,6 +58,9 @@ func SecurityPolicy(name string, findings []finding.Finding, dl checker.DetailLo
score += scoreProbeOnce(f.Probe, m, 6)
case securityPolicyContainsText.Probe:
score += scoreProbeOnce(f.Probe, m, 3)
case securityPolicyEnablesPrivateReporting.Probe:
pvrEnabled = true
pvrUnknown = false
case securityPolicyPresent.Probe:
m[f.Probe] = true
default:
Expand All @@ -62,6 +69,11 @@ func SecurityPolicy(name string, findings []finding.Finding, dl checker.DetailLo
}
case finding.OutcomeFalse:
logLevel = checker.DetailWarn
case finding.OutcomeNotApplicable:
logLevel = checker.DetailDebug
if f.Probe == securityPolicyEnablesPrivateReporting.Probe {
pvrUnknown = true
}
default:
logLevel = checker.DetailDebug
}
Expand All @@ -75,6 +87,18 @@ func SecurityPolicy(name string, findings []finding.Finding, dl checker.DetailLo
}
return checker.CreateMinScoreResult(name, "security policy file not detected")
}
// Adjust score based on private vulnerability reporting status.
// Enabled: keep score. Unknown: -1. Disabled: -2.
if pvrUnknown {
if score > 0 {
score--
}
} else if !pvrEnabled {
score -= 2
if score < 0 {
score = 0
}
}
return checker.CreateResultWithScore(name, "security policy file detected", score)
}

Expand Down
3 changes: 3 additions & 0 deletions checks/raw/security_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ func SecurityPolicy(c *checker.CheckRequest) (checker.SecurityPolicyData, error)
return checker.SecurityPolicyData{PolicyFiles: data.files}, nil
}

// TODO(#2465): Populate PrivateVulnerabilityReportingEnabled from the GitHub API.
// For now, mark as unknown (nil) so the evaluation can handle all platforms.

// Check if present in parent org.
// https#://docs.github.com/en/github/building-a-strong-community/creating-a-default-community-health-file.
client, err := c.RepoClient.GetOrgRepoClient(c.Ctx)
Expand Down
1 change: 1 addition & 0 deletions probes/securityPolicyEnablesPrivateReporting/def.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name: securityPolicyEnablesPrivateReporting
76 changes: 76 additions & 0 deletions probes/securityPolicyEnablesPrivateReporting/impl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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 securityPolicyEnablesPrivateReporting

import (
"embed"
"fmt"

"github.com/ossf/scorecard/v5/checker"
"github.com/ossf/scorecard/v5/finding"
"github.com/ossf/scorecard/v5/internal/checknames"
"github.com/ossf/scorecard/v5/internal/probes"
"github.com/ossf/scorecard/v5/probes/internal/utils/uerror"
)

func init() {
probes.MustRegister(Probe, Run, []checknames.CheckName{checknames.SecurityPolicy})
}

//go:embed *.yml
var fs embed.FS

const Probe = "securityPolicyEnablesPrivateReporting"

func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
if raw == nil {
return nil, "", fmt.Errorf("%w: raw", uerror.ErrNil)
}

pvr := raw.SecurityPolicyResults.PrivateVulnerabilityReportingEnabled

var findings []finding.Finding

if pvr == nil {
// Unknown — platform does not expose this signal.
f, err := finding.NewWith(fs, Probe, "private vulnerability reporting status unknown",
nil, finding.OutcomeNotApplicable)
if err != nil {
return nil, Probe, fmt.Errorf("create finding: %w", err)
}
f = f.WithRemediationMetadata(raw.Metadata.Metadata)
findings = append(findings, *f)
} else if *pvr {
// Enabled — good practice.
f, err := finding.NewWith(fs, Probe, "private vulnerability reporting is enabled",
nil, finding.OutcomeTrue)
if err != nil {
return nil, Probe, fmt.Errorf("create finding: %w", err)
}
f = f.WithRemediationMetadata(raw.Metadata.Metadata)
findings = append(findings, *f)
} else {
// Disabled — not enabled.
f, err := finding.NewWith(fs, Probe, "private vulnerability reporting is not enabled",
nil, finding.OutcomeFalse)
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
}
80 changes: 80 additions & 0 deletions probes/securityPolicyEnablesPrivateReporting/impl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2023 OpenSSF Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package securityPolicyEnablesPrivateReporting

import (
"testing"

"github.com/ossf/scorecard/v5/checker"
"github.com/ossf/scorecard/v5/finding"
)

func TestRun(t *testing.T) {
t.Run("returns not applicable when nil", func(t *testing.T) {
raw := &checker.RawResults{
SecurityPolicyResults: checker.SecurityPolicyData{
PrivateVulnerabilityReportingEnabled: nil,
},
Metadata: checker.MetadataData{Metadata: map[string]string{}},
}
findings, probe, err := Run(raw)
if err != nil {
t.Fatalf("Run() error = %v", err)
}
if probe != Probe {
t.Errorf("Run() probe = %v, want %v", probe, Probe)
}
if len(findings) != 1 {
t.Fatalf("Run() findings length = %v, want 1", len(findings))
}
if findings[0].Outcome != finding.OutcomeNotApplicable {
t.Errorf("Run() outcome = %v, want %v", findings[0].Outcome, finding.OutcomeNotApplicable)
}
})

t.Run("returns true when enabled", func(t *testing.T) {
enabled := true
raw := &checker.RawResults{
SecurityPolicyResults: checker.SecurityPolicyData{
PrivateVulnerabilityReportingEnabled: &enabled,
},
Metadata: checker.MetadataData{Metadata: map[string]string{}},
}
findings, _, err := Run(raw)
if err != nil {
t.Fatalf("Run() error = %v", err)
}
if findings[0].Outcome != finding.OutcomeTrue {
t.Errorf("Run() outcome = %v, want %v", findings[0].Outcome, finding.OutcomeTrue)
}
})

t.Run("returns false when disabled", func(t *testing.T) {
disabled := false
raw := &checker.RawResults{
SecurityPolicyResults: checker.SecurityPolicyData{
PrivateVulnerabilityReportingEnabled: &disabled,
},
Metadata: checker.MetadataData{Metadata: map[string]string{}},
}
findings, _, err := Run(raw)
if err != nil {
t.Fatalf("Run() error = %v", err)
}
if findings[0].Outcome != finding.OutcomeFalse {
t.Errorf("Run() outcome = %v, want %v", findings[0].Outcome, finding.OutcomeFalse)
}
})
}
Loading