mirror of
https://github.com/ossf/scorecard.git
synced 2024-09-17 11:57:12 +03:00
✨ Add dangerous workflow check with untrusted code checkout pattern (#1168)
* add dangerous workflow check with untrusted code checkout pattern Signed-off-by: Asra Ali <asraa@google.com> * update Signed-off-by: Asra Ali <asraa@google.com> * add env var Signed-off-by: Asra Ali <asraa@google.com> * fix comment Signed-off-by: Asra Ali <asraa@google.com> * add repos git checks.yaml Signed-off-by: Asra Ali <asraa@google.com> * update checks.md Signed-off-by: Asra Ali <asraa@google.com> * address comments Signed-off-by: Asra Ali <asraa@google.com> * fix merge Signed-off-by: Asra Ali <asraa@google.com> * add delete Signed-off-by: Asra Ali <asraa@google.com> * update docs Signed-off-by: Asra Ali <asraa@google.com> Co-authored-by: Naveen <172697+naveensrinivasan@users.noreply.github.com>
This commit is contained in:
parent
4dde356329
commit
1050b1cd60
268
checks/dangerous_workflow.go
Normal file
268
checks/dangerous_workflow.go
Normal file
@ -0,0 +1,268 @@
|
||||
// Copyright 2021 Security 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 checks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/ossf/scorecard/v3/checker"
|
||||
"github.com/ossf/scorecard/v3/checks/fileparser"
|
||||
sce "github.com/ossf/scorecard/v3/errors"
|
||||
)
|
||||
|
||||
// CheckDangerousWorkflow is the exported name for Dangerous-Workflow check.
|
||||
const CheckDangerousWorkflow = "Dangerous-Workflow"
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
registerCheck(CheckDangerousWorkflow, DangerousWorkflow)
|
||||
}
|
||||
|
||||
// Holds stateful data to pass thru callbacks.
|
||||
// Each field correpsonds to a dangerous GitHub workflow pattern, and
|
||||
// will hold true if the pattern is avoided, false otherwise.
|
||||
type patternCbData struct {
|
||||
workflowPattern map[string]bool
|
||||
}
|
||||
|
||||
// DangerousWorkflow runs Dangerous-Workflow check.
|
||||
func DangerousWorkflow(c *checker.CheckRequest) checker.CheckResult {
|
||||
// data is shared across all GitHub workflows.
|
||||
data := patternCbData{
|
||||
workflowPattern: make(map[string]bool),
|
||||
}
|
||||
err := CheckFilesContent(".github/workflows/*", false,
|
||||
c, validateGitHubActionWorkflowPatterns, &data)
|
||||
return createResultForDangerousWorkflowPatterns(data, err)
|
||||
}
|
||||
|
||||
// Check file content.
|
||||
func validateGitHubActionWorkflowPatterns(path string, content []byte, dl checker.DetailLogger,
|
||||
data FileCbData) (bool, error) {
|
||||
if !fileparser.IsWorkflowFile(path) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Verify the type of the data.
|
||||
pdata, ok := data.(*patternCbData)
|
||||
if !ok {
|
||||
// This never happens.
|
||||
panic("invalid type")
|
||||
}
|
||||
|
||||
if !CheckFileContainsCommands(content, "#") {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var workflow map[interface{}]interface{}
|
||||
err := yaml.Unmarshal(content, &workflow)
|
||||
if err != nil {
|
||||
return false,
|
||||
sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("yaml.Unmarshal: %v", err))
|
||||
}
|
||||
|
||||
// 1. Check for untrusted code checkout with pull_request_target and a ref
|
||||
if err := validateUntrustedCodeCheckout(workflow, path, dl, pdata); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// TODO: Check other dangerous patterns.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func validateUntrustedCodeCheckout(config map[interface{}]interface{}, path string,
|
||||
dl checker.DetailLogger, pdata *patternCbData) error {
|
||||
checkPullRequestTrigger, err := checkPullRequestTrigger(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if checkPullRequestTrigger {
|
||||
return validateUntrustedCodeCheckoutRef(config, path, dl, pdata)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateUntrustedCodeCheckoutRef(config map[interface{}]interface{}, path string,
|
||||
dl checker.DetailLogger, pdata *patternCbData) error {
|
||||
var jobs interface{}
|
||||
|
||||
// Now check if this is used with untrusted code checkout ref in jobs
|
||||
jobs, ok := config["jobs"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
mjobs, ok := jobs.(map[string]interface{})
|
||||
if !ok {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
||||
}
|
||||
|
||||
for _, value := range mjobs {
|
||||
job, ok := value.(map[string]interface{})
|
||||
if !ok {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
||||
}
|
||||
|
||||
if err := checkJobForUntrustedCodeCheckout(job, path, dl, pdata); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPullRequestTrigger(config map[interface{}]interface{}) (bool, error) {
|
||||
// Check event trigger (required) is pull_request_target
|
||||
trigger, ok := config["on"]
|
||||
if !ok {
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
||||
}
|
||||
|
||||
isPullRequestTrigger := false
|
||||
switch val := trigger.(type) {
|
||||
case string:
|
||||
if strings.EqualFold(val, "pull_request_target") {
|
||||
isPullRequestTrigger = true
|
||||
}
|
||||
case []string:
|
||||
for _, onVal := range val {
|
||||
if strings.EqualFold(onVal, "pull_request_target") {
|
||||
isPullRequestTrigger = true
|
||||
}
|
||||
}
|
||||
case map[interface{}]interface{}:
|
||||
for k := range val {
|
||||
key, ok := k.(string)
|
||||
if !ok {
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
||||
}
|
||||
if strings.EqualFold(key, "pull_request_target") {
|
||||
isPullRequestTrigger = true
|
||||
}
|
||||
}
|
||||
default:
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
||||
}
|
||||
return isPullRequestTrigger, nil
|
||||
}
|
||||
|
||||
func checkJobForUntrustedCodeCheckout(job map[string]interface{}, path string,
|
||||
dl checker.DetailLogger, pdata *patternCbData) error {
|
||||
steps, ok := job["steps"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
msteps, ok := steps.([]interface{})
|
||||
if !ok {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
||||
}
|
||||
// Check each step, which is a map, for checkouts with untrusted ref
|
||||
for _, step := range msteps {
|
||||
mstep, ok := step.(map[string]interface{})
|
||||
if !ok {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
||||
}
|
||||
// Check for a step that uses actions/checkout
|
||||
uses, ok := mstep["uses"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
muses, ok := uses.(string)
|
||||
if !ok {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
||||
}
|
||||
// Uses defaults if not defined.
|
||||
with, ok := mstep["with"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
mwith, ok := with.(map[string]interface{})
|
||||
if !ok {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
||||
}
|
||||
// Check for reference. If not defined for a pull_request_target event, this defaults to
|
||||
// the base branch of the pull request.
|
||||
ref, ok := mwith["ref"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
mref, ok := ref.(string)
|
||||
if !ok {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
||||
}
|
||||
if strings.Contains(muses, "actions/checkout") &&
|
||||
strings.Contains(mref, "github.event.pull_request.head.sha") {
|
||||
dl.Warn3(&checker.LogMessage{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
// TODO: set line correctly.
|
||||
Offset: 1,
|
||||
Text: fmt.Sprintf("untrusted code checkout '%v'", mref),
|
||||
// TODO: set Snippet.
|
||||
})
|
||||
// Detected untrusted checkout.
|
||||
pdata.workflowPattern["untrusted_checkout"] = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Calculate the workflow score.
|
||||
func calculateWorkflowScore(result patternCbData) int {
|
||||
// Start with a perfect score.
|
||||
score := float32(checker.MaxResultScore)
|
||||
|
||||
// pull_request_event indicates untrusted code checkout
|
||||
if ok := result.workflowPattern["untrusted_checkout"]; ok {
|
||||
score -= 10
|
||||
}
|
||||
|
||||
// We're done, calculate the final score.
|
||||
if score < checker.MinResultScore {
|
||||
return checker.MinResultScore
|
||||
}
|
||||
|
||||
return int(score)
|
||||
}
|
||||
|
||||
// Create the result.
|
||||
func createResultForDangerousWorkflowPatterns(result patternCbData, err error) checker.CheckResult {
|
||||
if err != nil {
|
||||
return checker.CreateRuntimeErrorResult(CheckDangerousWorkflow, err)
|
||||
}
|
||||
|
||||
score := calculateWorkflowScore(result)
|
||||
|
||||
if score != checker.MaxResultScore {
|
||||
return checker.CreateResultWithScore(CheckDangerousWorkflow,
|
||||
"dangerous workflow patterns detected", score)
|
||||
}
|
||||
|
||||
return checker.CreateMaxScoreResult(CheckDangerousWorkflow,
|
||||
"no dangerous workflow patterns detected")
|
||||
}
|
||||
|
||||
func testValidateGitHubActionDangerousWOrkflow(pathfn string,
|
||||
content []byte, dl checker.DetailLogger) checker.CheckResult {
|
||||
data := patternCbData{
|
||||
workflowPattern: make(map[string]bool),
|
||||
}
|
||||
_, err := validateGitHubActionWorkflowPatterns(pathfn, content, dl, &data)
|
||||
return createResultForDangerousWorkflowPatterns(data, err)
|
||||
}
|
109
checks/dangerous_workflow_test.go
Normal file
109
checks/dangerous_workflow_test.go
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright 2021 Security 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 checks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/ossf/scorecard/v3/checker"
|
||||
scut "github.com/ossf/scorecard/v3/utests"
|
||||
)
|
||||
|
||||
func TestGithubDangerousWorkflow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
expected scut.TestReturn
|
||||
}{
|
||||
{
|
||||
name: "Non-yaml file",
|
||||
filename: "./testdata/script.sh",
|
||||
expected: scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: checker.MaxResultScore,
|
||||
NumberOfWarn: 0,
|
||||
NumberOfInfo: 0,
|
||||
NumberOfDebug: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "run untrusted code checkout test",
|
||||
filename: "./testdata/github-workflow-dangerous-pattern-untrusted-checkout.yml",
|
||||
expected: scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: checker.MinResultScore,
|
||||
NumberOfWarn: 0,
|
||||
NumberOfInfo: 1,
|
||||
NumberOfDebug: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "run trusted code checkout test",
|
||||
filename: "./testdata/github-workflow-dangerous-pattern-trusted-checkout.yml",
|
||||
expected: scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: checker.MaxResultScore,
|
||||
NumberOfWarn: 0,
|
||||
NumberOfInfo: 0,
|
||||
NumberOfDebug: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "run default code checkout test",
|
||||
filename: "./testdata/github-workflow-dangerous-pattern-default-checkout.yml",
|
||||
expected: scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: checker.MaxResultScore,
|
||||
NumberOfWarn: 0,
|
||||
NumberOfInfo: 0,
|
||||
NumberOfDebug: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "run safe trigger with code checkout test",
|
||||
filename: "./testdata/github-workflow-dangerous-pattern-safe-trigger.yml",
|
||||
expected: scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: checker.MaxResultScore,
|
||||
NumberOfWarn: 0,
|
||||
NumberOfInfo: 0,
|
||||
NumberOfDebug: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
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()
|
||||
var content []byte
|
||||
var err error
|
||||
if tt.filename == "" {
|
||||
content = make([]byte, 0)
|
||||
} else {
|
||||
content, err = ioutil.ReadFile(tt.filename)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot read file: %w", err))
|
||||
}
|
||||
}
|
||||
dl := scut.TestDetailLogger{}
|
||||
r := testValidateGitHubActionDangerousWOrkflow(tt.filename, content, &dl)
|
||||
scut.ValidateTestReturn(t, tt.name, &tt.expected, &r, &dl)
|
||||
})
|
||||
}
|
||||
}
|
38
checks/testdata/github-workflow-dangerous-pattern-default-checkout.yml
vendored
Normal file
38
checks/testdata/github-workflow-dangerous-pattern-default-checkout.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright 2021 Security 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.
|
||||
on:
|
||||
pull_request_target
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
- run: |
|
||||
npm install
|
||||
npm build
|
||||
|
||||
- uses: completely/fakeaction@v2
|
||||
with:
|
||||
arg1: ${{ secrets.supersecret }}
|
||||
|
||||
- uses: fakerepo/comment-on-pr@v1
|
||||
with:
|
||||
message: |
|
||||
Thank you!
|
38
checks/testdata/github-workflow-dangerous-pattern-safe-trigger.yml
vendored
Normal file
38
checks/testdata/github-workflow-dangerous-pattern-safe-trigger.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright 2021 Security 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.
|
||||
on:
|
||||
pull_request
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: my-branch
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
- run: |
|
||||
npm install
|
||||
npm build
|
||||
|
||||
- uses: completely/fakeaction@v2
|
||||
with:
|
||||
arg1: ${{ secrets.supersecret }}
|
||||
|
||||
- uses: fakerepo/comment-on-pr@v1
|
||||
with:
|
||||
message: |
|
||||
Thank you!
|
38
checks/testdata/github-workflow-dangerous-pattern-trusted-checkout.yml
vendored
Normal file
38
checks/testdata/github-workflow-dangerous-pattern-trusted-checkout.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright 2021 Security 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.
|
||||
on:
|
||||
pull_request_target
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
- run: |
|
||||
npm install
|
||||
npm build
|
||||
|
||||
- uses: completely/fakeaction@v2
|
||||
with:
|
||||
arg1: ${{ secrets.supersecret }}
|
||||
|
||||
- uses: fakerepo/comment-on-pr@v1
|
||||
with:
|
||||
message: |
|
||||
Thank you!
|
38
checks/testdata/github-workflow-dangerous-pattern-untrusted-checkout.yml
vendored
Normal file
38
checks/testdata/github-workflow-dangerous-pattern-untrusted-checkout.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright 2021 Security 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.
|
||||
on:
|
||||
pull_request_target
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
- run: |
|
||||
npm install
|
||||
npm build
|
||||
|
||||
- uses: completely/fakeaction@v2
|
||||
with:
|
||||
arg1: ${{ secrets.supersecret }}
|
||||
|
||||
- uses: fakerepo/comment-on-pr@v1
|
||||
with:
|
||||
message: |
|
||||
Thank you!
|
16
cmd/root.go
16
cmd/root.go
@ -132,6 +132,16 @@ func isSupportedCheck(names []string, name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func getAllChecks() checker.CheckNameToFnMap {
|
||||
// Returns the full list of checks, given any environment variable constraints.
|
||||
possibleChecks := checks.AllChecks
|
||||
// TODO: Remove this to enable the DANGEROUS_WORKFLOW by default in the next release.
|
||||
if _, dangerousWorkflowCheck := os.LookupEnv("ENABLE_DANGEROUS_WORKFLOW"); !dangerousWorkflowCheck {
|
||||
delete(possibleChecks, checks.CheckDangerousWorkflow)
|
||||
}
|
||||
return possibleChecks
|
||||
}
|
||||
|
||||
func getEnabledChecks(sp *spol.ScorecardPolicy, argsChecks []string,
|
||||
supportedChecks []string, repoType string) (checker.CheckNameToFnMap, error) {
|
||||
enabledChecks := checker.CheckNameToFnMap{}
|
||||
@ -167,7 +177,7 @@ func getEnabledChecks(sp *spol.ScorecardPolicy, argsChecks []string,
|
||||
}
|
||||
default:
|
||||
// Enable all checks that are supported.
|
||||
for checkName := range checks.AllChecks {
|
||||
for checkName := range getAllChecks() {
|
||||
if !isSupportedCheck(supportedChecks, checkName) {
|
||||
continue
|
||||
}
|
||||
@ -476,7 +486,7 @@ func fetchGitRepositoryFromRubyGems(packageName string) (string, error) {
|
||||
// Enables checks by name.
|
||||
func enableCheck(checkName string, enabledChecks *checker.CheckNameToFnMap) bool {
|
||||
if enabledChecks != nil {
|
||||
for key, checkFn := range checks.AllChecks {
|
||||
for key, checkFn := range getAllChecks() {
|
||||
if strings.EqualFold(key, checkName) {
|
||||
(*enabledChecks)[key] = checkFn
|
||||
return true
|
||||
@ -507,7 +517,7 @@ func init() {
|
||||
&metaData, "metadata", []string{}, "metadata for the project. It can be multiple separated by commas")
|
||||
rootCmd.Flags().BoolVar(&showDetails, "show-details", false, "show extra details about each check")
|
||||
checkNames := []string{}
|
||||
for checkName := range checks.AllChecks {
|
||||
for checkName := range getAllChecks() {
|
||||
checkNames = append(checkNames, checkName)
|
||||
}
|
||||
rootCmd.Flags().StringSliceVar(&checksToRun, "checks", []string{},
|
||||
|
@ -196,6 +196,8 @@ func main() {
|
||||
delete(checksToRun, checks.CheckCITests)
|
||||
// TODO: Re-add Contributors check after fixing: #859.
|
||||
delete(checksToRun, checks.CheckContributors)
|
||||
// TODO: Add this in v4
|
||||
delete(checksToRun, checks.CheckDangerousWorkflow)
|
||||
for {
|
||||
req, err := subscriber.SynchronousPull()
|
||||
if err != nil {
|
||||
|
@ -237,6 +237,29 @@ participants.
|
||||
**Remediation steps**
|
||||
- Ask contributors to [join their respective organizations](https://docs.github.com/en/organizations/managing-membership-in-your-organization/inviting-users-to-join-your-organization), if they have not already. Otherwise, there is no remediation for this check; it simply provides insight into which organizations have contributed so that you can make a trust-based decision based on that information.
|
||||
|
||||
## Dangerous-Workflow
|
||||
|
||||
Risk: `High` (vulnerable to repository compromise)
|
||||
|
||||
This check determines whether the project's GitHub Action workflows has dangerous
|
||||
code patterns. Some examples of these patterns are untrusted code checkouts,
|
||||
logging github context and secrets, or use of potentially untrusted inputs in scripts.
|
||||
|
||||
The first code pattern checked is the misuse of potentially dangerous triggers.
|
||||
This checks if a `pull_request_target` workflow trigger was used in conjunction
|
||||
with an explicit pull request checkout. Workflows triggered with `pull_request_target`
|
||||
have write permission to the target repository and access to target repository
|
||||
secrets. With the PR checkout, PR authors may compromise the repository, for
|
||||
example, by using build scripts controlled by the author of the PR or reading
|
||||
token in memory. This check does not detect whether untrusted code checkouts are
|
||||
used safely, for example, only on pull request that have been assigned a label.
|
||||
|
||||
The highest score is awarded when all workflows avoid the dangerous code patterns.
|
||||
|
||||
|
||||
**Remediation steps**
|
||||
- Avoid the dangerous workflow patterns. See this [post](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/) for information on avoiding untrusted code checkouts.
|
||||
|
||||
## Dependency-Update-Tool
|
||||
|
||||
Risk: `High` (possibly vulnerable to attacks on known flaws)
|
||||
|
@ -629,3 +629,30 @@ checks:
|
||||
- >-
|
||||
Fix the vulnerabilities. The details of each vulnerability can be found
|
||||
on <https://osv.dev>.
|
||||
Dangerous-Workflow:
|
||||
risk: High
|
||||
tags: supply-chain, security, infrastructure
|
||||
repos: GitHub, local
|
||||
short: Determines if the project's GitHub Action workflows avoid dangerous patterns.
|
||||
description: |
|
||||
Risk: `High` (vulnerable to repository compromise)
|
||||
|
||||
This check determines whether the project's GitHub Action workflows has dangerous
|
||||
code patterns. Some examples of these patterns are untrusted code checkouts,
|
||||
logging github context and secrets, or use of potentially untrusted inputs in scripts.
|
||||
|
||||
The first code pattern checked is the misuse of potentially dangerous triggers.
|
||||
This checks if a `pull_request_target` workflow trigger was used in conjunction
|
||||
with an explicit pull request checkout. Workflows triggered with `pull_request_target`
|
||||
have write permission to the target repository and access to target repository
|
||||
secrets. With the PR checkout, PR authors may compromise the repository, for
|
||||
example, by using build scripts controlled by the author of the PR or reading
|
||||
token in memory. This check does not detect whether untrusted code checkouts are
|
||||
used safely, for example, only on pull request that have been assigned a label.
|
||||
|
||||
The highest score is awarded when all workflows avoid the dangerous code patterns.
|
||||
remediation:
|
||||
- >-
|
||||
Avoid the dangerous workflow patterns. See this [post](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)
|
||||
for information on avoiding untrusted code checkouts.
|
||||
|
||||
|
60
e2e/dangerous_workflow_test.go
Normal file
60
e2e/dangerous_workflow_test.go
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright 2021 Security 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 e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/ossf/scorecard/v3/checker"
|
||||
"github.com/ossf/scorecard/v3/checks"
|
||||
"github.com/ossf/scorecard/v3/clients/githubrepo"
|
||||
scut "github.com/ossf/scorecard/v3/utests"
|
||||
)
|
||||
|
||||
var _ = Describe("E2E TEST:"+checks.CheckTokenPermissions, func() {
|
||||
Context("E2E TEST:Validating dangerous workflow check", func() {
|
||||
It("Should return dangerous workflow works", func() {
|
||||
dl := scut.TestDetailLogger{}
|
||||
repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-dangerous-workflow-e2e")
|
||||
Expect(err).Should(BeNil())
|
||||
repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger)
|
||||
err = repoClient.InitRepo(repo)
|
||||
Expect(err).Should(BeNil())
|
||||
req := checker.CheckRequest{
|
||||
Ctx: context.Background(),
|
||||
RepoClient: repoClient,
|
||||
Repo: repo,
|
||||
Dlogger: &dl,
|
||||
}
|
||||
expected := scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: checker.MinResultScore,
|
||||
NumberOfWarn: 1,
|
||||
NumberOfInfo: 0,
|
||||
NumberOfDebug: 0,
|
||||
}
|
||||
result := checks.DangerousWorkflow(&req)
|
||||
// UPGRADEv2: to remove.
|
||||
// Old version.
|
||||
|
||||
Expect(result.Error).Should(BeNil())
|
||||
Expect(result.Pass).Should(BeFalse())
|
||||
// New version.
|
||||
Expect(scut.ValidateTestReturn(nil, "dangerous workflow", &expected, &result, &dl)).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user