mirror of
https://github.com/ossf/scorecard.git
synced 2024-09-11 08:55:27 +03:00
Using library to parse github workflows
This commit is contained in:
parent
f319aca82d
commit
3dc507b9e1
@ -150,3 +150,6 @@ linters-settings:
|
|||||||
- ptrToRefParam
|
- ptrToRefParam
|
||||||
- typeUnparen
|
- typeUnparen
|
||||||
- unnecessaryBlock
|
- unnecessaryBlock
|
||||||
|
wrapcheck:
|
||||||
|
ignorePackageGlobs:
|
||||||
|
- github.com/ossf/scorecard/v3/checks/fileparser
|
||||||
|
21
checks/fileparser/errors.go
Normal file
21
checks/fileparser/errors.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// 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 fileparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errInvalidGitHubWorkflow = errors.New("invalid GitHub workflow")
|
@ -20,7 +20,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"github.com/rhysd/actionlint"
|
||||||
|
|
||||||
sce "github.com/ossf/scorecard/v3/errors"
|
sce "github.com/ossf/scorecard/v3/errors"
|
||||||
)
|
)
|
||||||
@ -31,110 +31,51 @@ const defaultShellNonWindows = "bash"
|
|||||||
// defaultShellWindows is the default shell used for GitHub workflow actions for Windows.
|
// defaultShellWindows is the default shell used for GitHub workflow actions for Windows.
|
||||||
const defaultShellWindows = "pwsh"
|
const defaultShellWindows = "pwsh"
|
||||||
|
|
||||||
// Structure for workflow config.
|
// FormatActionlintError combines the errors into a single one.
|
||||||
// We only declare the fields we need.
|
func FormatActionlintError(errs []*actionlint.Error) error {
|
||||||
// Github workflows format: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
|
if len(errs) == 0 {
|
||||||
type GitHubActionWorkflowConfig struct {
|
|
||||||
Jobs map[string]GitHubActionWorkflowJob
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Github Action Workflow Job.
|
|
||||||
// We only declare the fields we need.
|
|
||||||
// Github workflows format: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
|
|
||||||
// nolint: govet
|
|
||||||
type GitHubActionWorkflowJob struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
Steps []GitHubActionWorkflowStep `yaml:"steps"`
|
|
||||||
Defaults struct {
|
|
||||||
Run struct {
|
|
||||||
Shell string `yaml:"shell"`
|
|
||||||
} `yaml:"run"`
|
|
||||||
} `yaml:"defaults"`
|
|
||||||
RunsOn stringOrSlice `yaml:"runs-on"`
|
|
||||||
Strategy struct {
|
|
||||||
// In most cases, the 'matrix' field will have a key of 'os' which is an array of strings, but there are
|
|
||||||
// some repos that have something like: 'matrix: ${{ fromJson(needs.matrix.outputs.latest) }}'.
|
|
||||||
Matrix interface{} `yaml:"matrix"`
|
|
||||||
} `yaml:"strategy"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Github Action Workflow Step.
|
|
||||||
// We only declare the fields we need.
|
|
||||||
// Github workflows format: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
|
|
||||||
type GitHubActionWorkflowStep struct {
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
ID string `yaml:"id"`
|
|
||||||
Shell string `yaml:"shell"`
|
|
||||||
Run string `yaml:"run"`
|
|
||||||
If string `yaml:"if"`
|
|
||||||
Uses stringWithLine `yaml:"uses"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// stringOrSlice is for fields that can be a single string or a slice of strings. If the field is a single string,
|
|
||||||
// this value will be a slice with a single string item.
|
|
||||||
type stringOrSlice []string
|
|
||||||
|
|
||||||
func (s *stringOrSlice) UnmarshalYAML(value *yaml.Node) error {
|
|
||||||
var stringSlice []string
|
|
||||||
err := value.Decode(&stringSlice)
|
|
||||||
if err == nil {
|
|
||||||
*s = stringSlice
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var single string
|
builder := strings.Builder{}
|
||||||
err = value.Decode(&single)
|
builder.WriteString(errInvalidGitHubWorkflow.Error() + ":")
|
||||||
if err != nil {
|
for _, err := range errs {
|
||||||
return sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("error decoding stringOrSlice Value: %v", err))
|
builder.WriteString("\n" + err.Error())
|
||||||
}
|
}
|
||||||
*s = []string{single}
|
return sce.WithMessage(sce.ErrScorecardInternal, builder.String())
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// stringWithLine is for when you want to keep track of the line number that the string came from.
|
|
||||||
type stringWithLine struct {
|
|
||||||
Value string
|
|
||||||
Line int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *stringWithLine) UnmarshalYAML(value *yaml.Node) error {
|
|
||||||
err := value.Decode(&ws.Value)
|
|
||||||
if err != nil {
|
|
||||||
return sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("error decoding stringWithLine Value: %v", err))
|
|
||||||
}
|
|
||||||
ws.Line = value.Line
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOSesForJob returns the OSes this job runs on.
|
// GetOSesForJob returns the OSes this job runs on.
|
||||||
func GetOSesForJob(job *GitHubActionWorkflowJob) ([]string, error) {
|
func GetOSesForJob(job *actionlint.Job) ([]string, error) {
|
||||||
// The 'runs-on' field either lists the OS'es directly, or it can have an expression '${{ matrix.os }}' which
|
// The 'runs-on' field either lists the OS'es directly, or it can have an expression '${{ matrix.os }}' which
|
||||||
// is where the OS'es are actually listed.
|
// is where the OS'es are actually listed.
|
||||||
getFromMatrix := len(job.RunsOn) == 1 && strings.Contains(job.RunsOn[0], "matrix.os")
|
|
||||||
if !getFromMatrix {
|
|
||||||
return job.RunsOn, nil
|
|
||||||
}
|
|
||||||
jobOSes := make([]string, 0)
|
jobOSes := make([]string, 0)
|
||||||
// nolint: nestif
|
getFromMatrix := len(job.RunsOn.Labels) == 1 && strings.Contains(job.RunsOn.Labels[0].Value, "matrix.os")
|
||||||
if m, ok := job.Strategy.Matrix.(map[string]interface{}); ok {
|
if !getFromMatrix {
|
||||||
if osVal, ok := m["os"]; ok {
|
// We can get the OSes straight from 'runs-on'.
|
||||||
if oses, ok := osVal.([]interface{}); ok {
|
for _, os := range job.RunsOn.Labels {
|
||||||
for _, os := range oses {
|
jobOSes = append(jobOSes, os.Value)
|
||||||
if strVal, ok := os.(string); ok {
|
}
|
||||||
jobOSes = append(jobOSes, strVal)
|
return jobOSes, nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return jobOSes, nil
|
for rowKey, rowValue := range job.Strategy.Matrix.Rows {
|
||||||
}
|
if rowKey != "os" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, os := range rowValue.Values {
|
||||||
|
jobOSes = append(jobOSes, strings.Trim(os.String(), "'\""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return jobOSes, sce.WithMessage(sce.ErrScorecardInternal,
|
|
||||||
fmt.Sprintf("unable to determine OS for job: %v", job.Name))
|
if len(jobOSes) == 0 {
|
||||||
|
return jobOSes, sce.WithMessage(sce.ErrScorecardInternal,
|
||||||
|
fmt.Sprintf("unable to determine OS for job: %v", job.Name.Value))
|
||||||
|
}
|
||||||
|
return jobOSes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// JobAlwaysRunsOnWindows returns true if the only OS that this job runs on is Windows.
|
// JobAlwaysRunsOnWindows returns true if the only OS that this job runs on is Windows.
|
||||||
func JobAlwaysRunsOnWindows(job *GitHubActionWorkflowJob) (bool, error) {
|
func JobAlwaysRunsOnWindows(job *actionlint.Job) (bool, error) {
|
||||||
jobOSes, err := GetOSesForJob(job)
|
jobOSes, err := GetOSesForJob(job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -148,13 +89,27 @@ func JobAlwaysRunsOnWindows(job *GitHubActionWorkflowJob) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetShellForStep returns the shell that is used to run the given step.
|
// GetShellForStep returns the shell that is used to run the given step.
|
||||||
func GetShellForStep(step *GitHubActionWorkflowStep, job *GitHubActionWorkflowJob) (string, error) {
|
func GetShellForStep(step *actionlint.Step, job *actionlint.Job) (string, error) {
|
||||||
// https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell.
|
// https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell.
|
||||||
if step.Shell != "" {
|
execRun, ok := step.Exec.(*actionlint.ExecRun)
|
||||||
return step.Shell, nil
|
if !ok {
|
||||||
|
jobName := ""
|
||||||
|
if job.Name != nil {
|
||||||
|
jobName = job.Name.Value
|
||||||
|
}
|
||||||
|
stepName := ""
|
||||||
|
if step.Name != nil {
|
||||||
|
stepName = step.Name.Value
|
||||||
|
}
|
||||||
|
return "", sce.WithMessage(sce.ErrScorecardInternal,
|
||||||
|
fmt.Sprintf("unable to parse step '%v' for job '%v'", jobName, stepName))
|
||||||
}
|
}
|
||||||
if job.Defaults.Run.Shell != "" {
|
if execRun != nil && execRun.Shell != nil && execRun.Shell.Value != "" {
|
||||||
return job.Defaults.Run.Shell, nil
|
return execRun.Shell.Value, nil
|
||||||
|
}
|
||||||
|
if job.Defaults != nil && job.Defaults.Run != nil && job.Defaults.Run.Shell != nil &&
|
||||||
|
job.Defaults.Run.Shell.Value != "" {
|
||||||
|
return job.Defaults.Run.Shell.Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
isStepWindows, err := IsStepWindows(step)
|
isStepWindows, err := IsStepWindows(step)
|
||||||
@ -177,7 +132,10 @@ func GetShellForStep(step *GitHubActionWorkflowStep, job *GitHubActionWorkflowJo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsStepWindows returns true if the step will be run on Windows.
|
// IsStepWindows returns true if the step will be run on Windows.
|
||||||
func IsStepWindows(step *GitHubActionWorkflowStep) (bool, error) {
|
func IsStepWindows(step *actionlint.Step) (bool, error) {
|
||||||
|
if step.If == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
windowsRegexes := []string{
|
windowsRegexes := []string{
|
||||||
// Looking for "if: runner.os == 'Windows'" (and variants)
|
// Looking for "if: runner.os == 'Windows'" (and variants)
|
||||||
`(?i)runner\.os\s*==\s*['"]windows['"]`,
|
`(?i)runner\.os\s*==\s*['"]windows['"]`,
|
||||||
@ -188,7 +146,7 @@ func IsStepWindows(step *GitHubActionWorkflowStep) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, windowsRegex := range windowsRegexes {
|
for _, windowsRegex := range windowsRegexes {
|
||||||
matches, err := regexp.MatchString(windowsRegex, step.If)
|
matches, err := regexp.MatchString(windowsRegex, step.If.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("error matching Windows regex: %v", err))
|
return false, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("error matching Windows regex: %v", err))
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"github.com/rhysd/actionlint"
|
||||||
"gotest.tools/assert/cmp"
|
"gotest.tools/assert/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -113,17 +113,17 @@ func TestGitHubWorkflowShell(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("cannot read file: %v", err)
|
t.Errorf("cannot read file: %v", err)
|
||||||
}
|
}
|
||||||
var workflow GitHubActionWorkflowConfig
|
|
||||||
err = yaml.Unmarshal(content, &workflow)
|
workflow, errs := actionlint.Parse(content)
|
||||||
if err != nil {
|
if len(errs) > 0 && workflow == nil {
|
||||||
t.Errorf("cannot unmarshal file: %v", err)
|
t.Errorf("cannot unmarshal file: %v", errs[0])
|
||||||
}
|
}
|
||||||
actualShells := make([]string, 0)
|
actualShells := make([]string, 0)
|
||||||
for _, job := range workflow.Jobs {
|
for _, job := range workflow.Jobs {
|
||||||
job := job
|
job := job
|
||||||
for _, step := range job.Steps {
|
for _, step := range job.Steps {
|
||||||
step := step
|
step := step
|
||||||
shell, err := GetShellForStep(&step, &job)
|
shell, err := GetShellForStep(step, job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error getting shell: %v", err)
|
t.Errorf("error getting shell: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"github.com/rhysd/actionlint"
|
||||||
|
|
||||||
"github.com/ossf/scorecard/v3/checker"
|
"github.com/ossf/scorecard/v3/checker"
|
||||||
"github.com/ossf/scorecard/v3/checks/fileparser"
|
"github.com/ossf/scorecard/v3/checks/fileparser"
|
||||||
@ -53,25 +53,24 @@ func TokenPermissions(c *checker.CheckRequest) checker.CheckResult {
|
|||||||
return createResultForLeastPrivilegeTokens(data, err)
|
return createResultForLeastPrivilegeTokens(data, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatePermission(key string, value interface{}, path string,
|
func validatePermission(permissionKey string, permissionValue *actionlint.PermissionScope, path string,
|
||||||
dl checker.DetailLogger, pPermissions map[string]bool,
|
dl checker.DetailLogger, pPermissions map[string]bool,
|
||||||
ignoredPermissions map[string]bool) error {
|
ignoredPermissions map[string]bool) error {
|
||||||
val, ok := value.(string)
|
if permissionValue.Value == nil {
|
||||||
if !ok {
|
|
||||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
||||||
}
|
}
|
||||||
|
val := permissionValue.Value.Value
|
||||||
if strings.EqualFold(val, "write") {
|
if strings.EqualFold(val, "write") {
|
||||||
if isPermissionOfInterest(key, ignoredPermissions) {
|
if isPermissionOfInterest(permissionKey, ignoredPermissions) {
|
||||||
dl.Warn3(&checker.LogMessage{
|
dl.Warn3(&checker.LogMessage{
|
||||||
Path: path,
|
Path: path,
|
||||||
Type: checker.FileTypeSource,
|
Type: checker.FileTypeSource,
|
||||||
// TODO: set line.
|
// TODO: set line.
|
||||||
Offset: 1,
|
Offset: 1,
|
||||||
Text: fmt.Sprintf("'%v' permission set to '%v'", key, val),
|
Text: fmt.Sprintf("'%v' permission set to '%v'", permissionKey, val),
|
||||||
// TODO: set Snippet.
|
// TODO: set Snippet.
|
||||||
})
|
})
|
||||||
recordPermissionWrite(key, pPermissions)
|
recordPermissionWrite(permissionKey, pPermissions)
|
||||||
} else {
|
} else {
|
||||||
// Only log for debugging, otherwise
|
// Only log for debugging, otherwise
|
||||||
// it may confuse users.
|
// it may confuse users.
|
||||||
@ -80,7 +79,7 @@ func validatePermission(key string, value interface{}, path string,
|
|||||||
Type: checker.FileTypeSource,
|
Type: checker.FileTypeSource,
|
||||||
// TODO: set line.
|
// TODO: set line.
|
||||||
Offset: 1,
|
Offset: 1,
|
||||||
Text: fmt.Sprintf("'%v' permission set to '%v'", key, val),
|
Text: fmt.Sprintf("'%v' permission set to '%v'", permissionKey, val),
|
||||||
// TODO: set Snippet.
|
// TODO: set Snippet.
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -92,22 +91,16 @@ func validatePermission(key string, value interface{}, path string,
|
|||||||
Type: checker.FileTypeSource,
|
Type: checker.FileTypeSource,
|
||||||
// TODO: set line correctly.
|
// TODO: set line correctly.
|
||||||
Offset: 1,
|
Offset: 1,
|
||||||
Text: fmt.Sprintf("'%v' permission set to '%v'", key, val),
|
Text: fmt.Sprintf("'%v' permission set to '%v'", permissionKey, val),
|
||||||
// TODO: set Snippet.
|
// TODO: set Snippet.
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateMapPermissions(values map[interface{}]interface{}, path string,
|
func validateMapPermissions(scopes map[string]*actionlint.PermissionScope, path string,
|
||||||
dl checker.DetailLogger, pPermissions map[string]bool,
|
dl checker.DetailLogger, pPermissions map[string]bool,
|
||||||
ignoredPermissions map[string]bool) error {
|
ignoredPermissions map[string]bool) error {
|
||||||
// Iterate over the permission, verify keys and values are strings.
|
for key, v := range scopes {
|
||||||
for k, v := range values {
|
|
||||||
key, ok := k.(string)
|
|
||||||
if !ok {
|
|
||||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validatePermission(key, v, path, dl, pPermissions, ignoredPermissions); err != nil {
|
if err := validatePermission(key, v, path, dl, pPermissions, ignoredPermissions); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -125,14 +118,12 @@ func recordAllPermissionsWrite(pPermissions map[string]bool) {
|
|||||||
pPermissions["all"] = true
|
pPermissions["all"] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatePermissions(permissions interface{}, path string,
|
func validatePermissions(permissions *actionlint.Permissions, path string,
|
||||||
dl checker.DetailLogger, pPermissions map[string]bool,
|
dl checker.DetailLogger, pPermissions map[string]bool,
|
||||||
ignoredPermissions map[string]bool) error {
|
ignoredPermissions map[string]bool) error {
|
||||||
// Check the type of our values.
|
allIsSet := permissions != nil && permissions.All != nil && permissions.All.Value != ""
|
||||||
switch val := permissions.(type) {
|
scopeIsSet := permissions != nil && len(permissions.Scopes) > 0
|
||||||
// Empty string is nil type.
|
if permissions == nil || (!allIsSet && !scopeIsSet) {
|
||||||
// It defaults to 'none'
|
|
||||||
case nil:
|
|
||||||
dl.Info3(&checker.LogMessage{
|
dl.Info3(&checker.LogMessage{
|
||||||
Path: path,
|
Path: path,
|
||||||
Type: checker.FileTypeSource,
|
Type: checker.FileTypeSource,
|
||||||
@ -141,8 +132,9 @@ func validatePermissions(permissions interface{}, path string,
|
|||||||
Text: "permissions set to 'none'",
|
Text: "permissions set to 'none'",
|
||||||
// TODO: set Snippet.
|
// TODO: set Snippet.
|
||||||
})
|
})
|
||||||
// String type.
|
}
|
||||||
case string:
|
if allIsSet {
|
||||||
|
val := permissions.All.Value
|
||||||
if !strings.EqualFold(val, "read-all") && val != "" {
|
if !strings.EqualFold(val, "read-all") && val != "" {
|
||||||
dl.Warn3(&checker.LogMessage{
|
dl.Warn3(&checker.LogMessage{
|
||||||
Path: path,
|
Path: path,
|
||||||
@ -164,25 +156,17 @@ func validatePermissions(permissions interface{}, path string,
|
|||||||
Text: fmt.Sprintf("permissions set to '%v'", val),
|
Text: fmt.Sprintf("permissions set to '%v'", val),
|
||||||
// TODO: set Snippet.
|
// TODO: set Snippet.
|
||||||
})
|
})
|
||||||
|
} else /* scopeIsSet == true */ if err := validateMapPermissions(permissions.Scopes, path, dl, pPermissions,
|
||||||
// Map type.
|
ignoredPermissions); err != nil {
|
||||||
case map[interface{}]interface{}:
|
return err
|
||||||
if err := validateMapPermissions(val, path, dl, pPermissions, ignoredPermissions); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalid type.
|
|
||||||
default:
|
|
||||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateTopLevelPermissions(config map[interface{}]interface{}, path string,
|
func validateTopLevelPermissions(workflow *actionlint.Workflow, path string,
|
||||||
dl checker.DetailLogger, pdata *permissionCbData) error {
|
dl checker.DetailLogger, pdata *permissionCbData) error {
|
||||||
// Check if permissions are set explicitly.
|
// Check if permissions are set explicitly.
|
||||||
permissions, ok := config["permissions"]
|
if workflow.Permissions == nil {
|
||||||
if !ok {
|
|
||||||
dl.Warn3(&checker.LogMessage{
|
dl.Warn3(&checker.LogMessage{
|
||||||
Path: path,
|
Path: path,
|
||||||
Type: checker.FileTypeSource,
|
Type: checker.FileTypeSource,
|
||||||
@ -193,35 +177,18 @@ func validateTopLevelPermissions(config map[interface{}]interface{}, path string
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return validatePermissions(permissions, path, dl,
|
return validatePermissions(workflow.Permissions, path, dl,
|
||||||
pdata.topLevelWritePermissions, map[string]bool{})
|
pdata.topLevelWritePermissions, map[string]bool{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRunLevelPermissions(config map[interface{}]interface{}, path string,
|
func validateRunLevelPermissions(workflow *actionlint.Workflow, path string,
|
||||||
dl checker.DetailLogger, pdata *permissionCbData,
|
dl checker.DetailLogger, pdata *permissionCbData,
|
||||||
ignoredPermissions map[string]bool) error {
|
ignoredPermissions map[string]bool) error {
|
||||||
var jobs interface{}
|
for _, job := range workflow.Jobs {
|
||||||
|
|
||||||
jobs, ok := config["jobs"]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mjobs, ok := jobs.(map[interface{}]interface{})
|
|
||||||
if !ok {
|
|
||||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, value := range mjobs {
|
|
||||||
job, ok := value.(map[interface{}]interface{})
|
|
||||||
if !ok {
|
|
||||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
|
||||||
}
|
|
||||||
// Run-level permissions may be left undefined.
|
// Run-level permissions may be left undefined.
|
||||||
// For most workflows, no write permissions are needed,
|
// For most workflows, no write permissions are needed,
|
||||||
// so only top-level read-only permissions need to be declared.
|
// so only top-level read-only permissions need to be declared.
|
||||||
permissions, ok := job["permissions"]
|
if job.Permissions == nil {
|
||||||
if !ok {
|
|
||||||
dl.Debug3(&checker.LogMessage{
|
dl.Debug3(&checker.LogMessage{
|
||||||
Path: path,
|
Path: path,
|
||||||
Type: checker.FileTypeSource,
|
Type: checker.FileTypeSource,
|
||||||
@ -230,8 +197,7 @@ func validateRunLevelPermissions(config map[interface{}]interface{}, path string
|
|||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err := validatePermissions(permissions, path, dl,
|
err := validatePermissions(job.Permissions, path, dl, pdata.runLevelWritePermissions, ignoredPermissions)
|
||||||
pdata.runLevelWritePermissions, ignoredPermissions)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -375,11 +341,9 @@ func validateGitHubActionTokenPermissions(path string, content []byte,
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var workflow map[interface{}]interface{}
|
workflow, errs := actionlint.Parse(content)
|
||||||
err := yaml.Unmarshal(content, &workflow)
|
if len(errs) > 0 && workflow == nil {
|
||||||
if err != nil {
|
return false, fileparser.FormatActionlintError(errs)
|
||||||
return false,
|
|
||||||
sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("yaml.Unmarshal: %v", err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Top-level permission definitions.
|
// 1. Top-level permission definitions.
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||||
"gopkg.in/yaml.v3"
|
"github.com/rhysd/actionlint"
|
||||||
|
|
||||||
"github.com/ossf/scorecard/v3/checker"
|
"github.com/ossf/scorecard/v3/checker"
|
||||||
"github.com/ossf/scorecard/v3/checks/fileparser"
|
"github.com/ossf/scorecard/v3/checks/fileparser"
|
||||||
@ -451,11 +451,11 @@ func validateGitHubWorkflowIsFreeOfInsecureDownloads(pathfn string, content []by
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var workflow fileparser.GitHubActionWorkflowConfig
|
workflow, errs := actionlint.Parse(content)
|
||||||
err := yaml.Unmarshal(content, &workflow)
|
if len(errs) > 0 && workflow == nil {
|
||||||
if err != nil {
|
// actionlint is a linter, so it will return errors when the yaml file does not meet its linting standards.
|
||||||
return false, sce.WithMessage(sce.ErrScorecardInternal,
|
// Often we don't care about these errors.
|
||||||
fmt.Sprintf("%v: %v", errInternalInvalidYamlFile, err))
|
return false, fileparser.FormatActionlintError(errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
githubVarRegex := regexp.MustCompile(`{{[^{}]*}}`)
|
githubVarRegex := regexp.MustCompile(`{{[^{}]*}}`)
|
||||||
@ -465,12 +465,12 @@ func validateGitHubWorkflowIsFreeOfInsecureDownloads(pathfn string, content []by
|
|||||||
job := job
|
job := job
|
||||||
for _, step := range job.Steps {
|
for _, step := range job.Steps {
|
||||||
step := step
|
step := step
|
||||||
if step.Run == "" {
|
if step.Exec.Kind() != actionlint.ExecKindRun {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
|
// https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
|
||||||
shell, err := fileparser.GetShellForStep(&step, &job)
|
shell, err := fileparser.GetShellForStep(step, job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -479,7 +479,7 @@ func validateGitHubWorkflowIsFreeOfInsecureDownloads(pathfn string, content []by
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
run := step.Run
|
run := step.Exec.(*actionlint.ExecRun).Run.Value
|
||||||
// We replace the `${{ github.variable }}` to avoid shell parsing failures.
|
// We replace the `${{ github.variable }}` to avoid shell parsing failures.
|
||||||
script := githubVarRegex.ReplaceAll([]byte(run), []byte("GITHUB_REDACTED_VAR"))
|
script := githubVarRegex.ReplaceAll([]byte(run), []byte("GITHUB_REDACTED_VAR"))
|
||||||
scriptContent = fmt.Sprintf("%v\n%v", scriptContent, string(script))
|
scriptContent = fmt.Sprintf("%v\n%v", scriptContent, string(script))
|
||||||
@ -487,6 +487,7 @@ func validateGitHubWorkflowIsFreeOfInsecureDownloads(pathfn string, content []by
|
|||||||
}
|
}
|
||||||
|
|
||||||
if scriptContent != "" {
|
if scriptContent != "" {
|
||||||
|
var err error
|
||||||
validated, err = validateShellFile(pathfn, []byte(scriptContent), dl)
|
validated, err = validateShellFile(pathfn, []byte(scriptContent), dl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -534,30 +535,42 @@ func validateGitHubActionWorkflow(pathfn string, content []byte,
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var workflow fileparser.GitHubActionWorkflowConfig
|
workflow, errs := actionlint.Parse(content)
|
||||||
err := yaml.Unmarshal(content, &workflow)
|
if len(errs) > 0 && workflow == nil {
|
||||||
if err != nil {
|
// actionlint is a linter, so it will return errors when the yaml file does not meet its linting standards.
|
||||||
return false, sce.WithMessage(sce.ErrScorecardInternal,
|
// Often we don't care about these errors.
|
||||||
fmt.Sprintf("%v: %v", errInternalInvalidYamlFile, err))
|
return false, fileparser.FormatActionlintError(errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
hashRegex := regexp.MustCompile(`^.*@[a-f\d]{40,}`)
|
hashRegex := regexp.MustCompile(`^.*@[a-f\d]{40,}`)
|
||||||
for jobName, job := range workflow.Jobs {
|
for jobName, job := range workflow.Jobs {
|
||||||
if len(job.Name) > 0 {
|
if job.Name != nil && len(job.Name.Value) > 0 {
|
||||||
jobName = job.Name
|
jobName = job.Name.Value
|
||||||
}
|
}
|
||||||
for _, step := range job.Steps {
|
for _, step := range job.Steps {
|
||||||
if len(step.Uses.Value) > 0 {
|
if step.Exec.Kind() != actionlint.ExecKindAction {
|
||||||
// Ensure a hash at least as large as SHA1 is used (40 hex characters).
|
continue
|
||||||
// Example: action-name@hash
|
}
|
||||||
match := hashRegex.Match([]byte(step.Uses.Value))
|
execAction, ok := step.Exec.(*actionlint.ExecAction)
|
||||||
if !match {
|
if !ok {
|
||||||
dl.Warn3(&checker.LogMessage{
|
stepName := ""
|
||||||
Path: pathfn, Type: checker.FileTypeSource, Offset: step.Uses.Line, Snippet: step.Uses.Value,
|
if step.Name != nil {
|
||||||
Text: fmt.Sprintf("dependency not pinned by hash (job '%v')", jobName),
|
stepName = step.Name.Value
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false, sce.WithMessage(sce.ErrScorecardInternal,
|
||||||
|
fmt.Sprintf("unable to parse step '%v' for job '%v'", jobName, stepName))
|
||||||
|
}
|
||||||
|
// Ensure a hash at least as large as SHA1 is used (40 hex characters).
|
||||||
|
// Example: action-name@hash
|
||||||
|
match := hashRegex.Match([]byte(execAction.Uses.Value))
|
||||||
|
if !match {
|
||||||
|
dl.Warn3(&checker.LogMessage{
|
||||||
|
Path: pathfn, Type: checker.FileTypeSource, Offset: execAction.Uses.Pos.Line, Snippet: execAction.Uses.Value,
|
||||||
|
Text: fmt.Sprintf("dependency not pinned by hash (job '%v')", jobName),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
githubOwned := fileparser.IsGitHubOwnedAction(execAction.Uses.Value)
|
githubOwned := fileparser.IsGitHubOwnedAction(execAction.Uses.Value)
|
||||||
addWorkflowPinnedResult(pdata, match, githubOwned)
|
addWorkflowPinnedResult(pdata, match, githubOwned)
|
||||||
}
|
}
|
||||||
|
@ -933,7 +933,7 @@ func TestGitHubWorkflowUsesLineNumber(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
dependency: "docker/build-push-action@1.2.3",
|
dependency: "docker/build-push-action@1.2.3",
|
||||||
lineNumber: 26,
|
lineNumber: 24,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -20,7 +20,5 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v1
|
||||||
run: echo "unpinned dependency"
|
|
||||||
- name: some name
|
- name: some name
|
||||||
run: echo "unpinned dependency"
|
|
||||||
uses: docker/build-push-action@1.2.3
|
uses: docker/build-push-action@1.2.3
|
||||||
|
12
go.mod
12
go.mod
@ -35,7 +35,10 @@ require (
|
|||||||
mvdan.cc/sh/v3 v3.4.0
|
mvdan.cc/sh/v3 v3.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require gotest.tools v2.2.0+incompatible
|
require (
|
||||||
|
github.com/rhysd/actionlint v1.6.7
|
||||||
|
gotest.tools v2.2.0+incompatible
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.94.1 // indirect
|
cloud.google.com/go v0.94.1 // indirect
|
||||||
@ -52,6 +55,7 @@ require (
|
|||||||
github.com/docker/docker v20.10.7+incompatible // indirect
|
github.com/docker/docker v20.10.7+incompatible // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.6.3 // indirect
|
github.com/docker/docker-credential-helpers v0.6.3 // indirect
|
||||||
github.com/emirpasic/gods v1.12.0 // indirect
|
github.com/emirpasic/gods v1.12.0 // indirect
|
||||||
|
github.com/fatih/color v1.12.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||||
github.com/go-git/gcfg v1.5.0 // indirect
|
github.com/go-git/gcfg v1.5.0 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
||||||
@ -69,12 +73,16 @@ require (
|
|||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
|
||||||
github.com/klauspost/compress v1.13.5 // indirect
|
github.com/klauspost/compress v1.13.5 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/nxadm/tail v1.4.8 // indirect
|
github.com/nxadm/tail v1.4.8 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/robfig/cron v1.2.0 // indirect
|
||||||
github.com/sergi/go-diff v1.1.0 // indirect
|
github.com/sergi/go-diff v1.1.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
16
go.sum
16
go.sum
@ -483,6 +483,7 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ
|
|||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/dvyukov/go-fuzz v0.0.0-20210602112143-b1f3d6f4ef4e/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
@ -504,6 +505,8 @@ github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
|
|||||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
|
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
|
||||||
|
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||||
@ -931,6 +934,8 @@ github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcncea
|
|||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||||
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
|
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
|
||||||
github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
|
github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
|
||||||
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
||||||
@ -941,9 +946,12 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
|||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||||
|
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
@ -1127,6 +1135,12 @@ github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:
|
|||||||
github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k=
|
github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k=
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||||
|
github.com/rhysd/actionlint v1.6.7 h1:cOojXJ/N3OAoOaTo8zEEA6uWovUC+0yP3dLYEZnRWg4=
|
||||||
|
github.com/rhysd/actionlint v1.6.7/go.mod h1:vFbgcUNEK84kr9Lb3vEfqjTov/Q3X5py+ppKihWgH+w=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
|
||||||
|
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
|
Loading…
Reference in New Issue
Block a user