Go: Bump github.com/securego/gosec/v2 from 2.17.0 to 2.18.2 (#7167)

Bumps [github.com/securego/gosec/v2](https://github.com/securego/gosec) from 2.17.0 to 2.18.2.
- [Release notes](https://github.com/securego/gosec/releases)
- [Changelog](https://github.com/securego/gosec/blob/master/.goreleaser.yml)
- [Commits](https://github.com/securego/gosec/compare/v2.17.0...v2.18.2)

---
updated-dependencies:
- dependency-name: github.com/securego/gosec/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
dependabot[bot]
2023-12-18 08:41:10 +00:00
committed by GitHub
parent f040a380c9
commit 6ecaa8bc30
46 changed files with 1186 additions and 824 deletions

14
go.mod
View File

@@ -31,8 +31,8 @@ require (
github.com/mitchellh/go-ps v1.0.0
github.com/olekukonko/tablewriter v0.0.5
github.com/onsi/ginkgo v1.16.5
github.com/onsi/ginkgo/v2 v2.12.1
github.com/onsi/gomega v1.27.10
github.com/onsi/ginkgo/v2 v2.13.0
github.com/onsi/gomega v1.28.1
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b
github.com/openshift/api v0.0.0-20220525145417-ee5b62754c68
@@ -44,7 +44,7 @@ require (
github.com/posener/complete v1.2.3
github.com/redhat-developer/service-binding-operator v1.0.1-0.20211222115357-5b7bbba3bfb3
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
github.com/securego/gosec/v2 v2.17.0
github.com/securego/gosec/v2 v2.18.2
github.com/segmentio/backo-go v1.0.1
github.com/sethvargo/go-envconfig v0.9.0
github.com/spf13/afero v1.6.0
@@ -52,7 +52,7 @@ require (
github.com/spf13/pflag v1.0.5
github.com/tidwall/gjson v1.17.0
github.com/zalando/go-keyring v0.2.3
golang.org/x/sync v0.3.0
golang.org/x/sync v0.4.0
golang.org/x/sys v0.14.0
golang.org/x/term v0.14.0
golang.org/x/text v0.14.0
@@ -133,7 +133,7 @@ require (
github.com/google/licensecheck v0.3.1 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -195,11 +195,11 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/oauth2 v0.7.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.13.0 // indirect
golang.org/x/tools v0.14.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230525154841-bd750badd5c6 // indirect

28
go.sum generated
View File

@@ -678,8 +678,8 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
@@ -964,8 +964,8 @@ github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8Ay
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc=
github.com/onsi/ginkgo/v2 v2.12.1 h1:uHNEO1RP2SpuZApSkel9nEh1/Mu+hmQe7Q+Pepg5OYA=
github.com/onsi/ginkgo/v2 v2.12.1/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@@ -984,8 +984,8 @@ github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ
github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA=
github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
@@ -1128,8 +1128,8 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/securego/gosec/v2 v2.17.0 h1:ZpAStTDKY39insEG9OH6kV3IkhQZPTq9a9eGOLOjcdI=
github.com/securego/gosec/v2 v2.17.0/go.mod h1:lt+mgC91VSmriVoJLentrMkRCYs+HLTBnUFUBuhV2hc=
github.com/securego/gosec/v2 v2.18.2 h1:DkDt3wCiOtAHf1XkiXZBhQ6m6mK/b9T/wD257R3/c+I=
github.com/securego/gosec/v2 v2.18.2/go.mod h1:xUuqSF6i0So56Y2wwohWAmB07EdBkUN6crbLlHwbyJs=
github.com/segmentio/analytics-go/v3 v3.2.1 h1:G+f90zxtc1p9G+WigVyTR0xNfOghOGs/PYAlljLOyeg=
github.com/segmentio/analytics-go/v3 v3.2.1/go.mod h1:p8owAF8X+5o27jmvUognuXxdtqvSGtD0ZrfY2kcS9bE=
github.com/segmentio/backo-go v1.0.1 h1:68RQccglxZeyURy93ASB/2kc9QudzgIDexJ927N++y4=
@@ -1426,8 +1426,8 @@ golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1534,8 +1534,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1771,8 +1771,8 @@ golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -1,9 +0,0 @@
language: go
go:
- 1.4.3
- 1.5.3
- tip
script:
- go test -v ./...

10
vendor/github.com/google/uuid/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# Changelog
## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18)
### Bug Fixes
* Use .EqualFold() to parse urn prefixed UUIDs ([#118](https://github.com/google/uuid/issues/118)) ([574e687](https://github.com/google/uuid/commit/574e6874943741fb99d41764c705173ada5293f0))
## Changelog

View File

@@ -2,6 +2,22 @@
We definitely welcome patches and contribution to this project!
### Tips
Commits must be formatted according to the [Conventional Commits Specification](https://www.conventionalcommits.org).
Always try to include a test case! If it is not possible or not necessary,
please explain why in the pull request description.
### Releasing
Commits that would precipitate a SemVer change, as desrcibed in the Conventional
Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action)
to create a release candidate pull request. Once submitted, `release-please`
will create a release.
For tips on how to work with `release-please`, see its documentation.
### Legal requirements
In order to protect both you and ourselves, you will need to sign the

View File

@@ -1,6 +1,6 @@
# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master)
# uuid
The uuid package generates and inspects UUIDs based on
[RFC 4122](http://tools.ietf.org/html/rfc4122)
[RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122)
and DCE 1.1: Authentication and Security Services.
This package is based on the github.com/pborman/uuid package (previously named
@@ -9,10 +9,12 @@ a UUID is a 16 byte array rather than a byte slice. One loss due to this
change is the ability to represent an invalid UUID (vs a NIL UUID).
###### Install
`go get github.com/google/uuid`
```sh
go get github.com/google/uuid
```
###### Documentation
[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid)
[![Go Reference](https://pkg.go.dev/badge/github.com/google/uuid.svg)](https://pkg.go.dev/github.com/google/uuid)
Full `go doc` style documentation for the package can be viewed online without
installing this package by using the GoDoc site here:

View File

@@ -7,6 +7,6 @@
package uuid
// getHardwareInterface returns nil values for the JS version of the code.
// This remvoves the "net" dependency, because it is not used in the browser.
// This removes the "net" dependency, because it is not used in the browser.
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
func getHardwareInterface(name string) (string, []byte) { return "", nil }

View File

@@ -69,7 +69,7 @@ func Parse(s string) (UUID, error) {
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36 + 9:
if strings.ToLower(s[:9]) != "urn:uuid:" {
if !strings.EqualFold(s[:9], "urn:uuid:") {
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
}
s = s[9:]
@@ -101,7 +101,8 @@ func Parse(s string) (UUID, error) {
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
24, 26, 28, 30, 32, 34,
} {
v, ok := xtob(s[x], s[x+1])
if !ok {
return uuid, errors.New("invalid UUID format")
@@ -117,7 +118,7 @@ func ParseBytes(b []byte) (UUID, error) {
switch len(b) {
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) {
if !bytes.EqualFold(b[:9], []byte("urn:uuid:")) {
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
}
b = b[9:]
@@ -145,7 +146,8 @@ func ParseBytes(b []byte) (UUID, error) {
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
24, 26, 28, 30, 32, 34,
} {
v, ok := xtob(b[x], b[x+1])
if !ok {
return uuid, errors.New("invalid UUID format")

View File

@@ -1,3 +1,9 @@
## 2.13.0
### Features
Add PreviewSpect() to enable programmatic preview access to the suite report (fixes #1225)
## 2.12.1
### Fixes

View File

@@ -248,31 +248,13 @@ func RunSpecs(t GinkgoTestingT, description string, args ...interface{}) bool {
exitIfErr(types.GinkgoErrors.RerunningSuite())
}
suiteDidRun = true
suiteLabels := Labels{}
configErrors := []error{}
for _, arg := range args {
switch arg := arg.(type) {
case types.SuiteConfig:
suiteConfig = arg
case types.ReporterConfig:
reporterConfig = arg
case Labels:
suiteLabels = append(suiteLabels, arg...)
default:
configErrors = append(configErrors, types.GinkgoErrors.UnknownTypePassedToRunSpecs(arg))
}
err := global.PushClone()
if err != nil {
exitIfErr(err)
}
exitIfErrors(configErrors)
defer global.PopClone()
configErrors = types.VetConfig(flagSet, suiteConfig, reporterConfig)
if len(configErrors) > 0 {
fmt.Fprintf(formatter.ColorableStdErr, formatter.F("{{red}}Ginkgo detected configuration issues:{{/}}\n"))
for _, err := range configErrors {
fmt.Fprintf(formatter.ColorableStdErr, err.Error())
}
os.Exit(1)
}
suiteLabels := extractSuiteConfiguration(args)
var reporter reporters.Reporter
if suiteConfig.ParallelTotal == 1 {
@@ -308,9 +290,8 @@ func RunSpecs(t GinkgoTestingT, description string, args ...interface{}) bool {
registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig)
}
err := global.Suite.BuildTree()
err = global.Suite.BuildTree()
exitIfErr(err)
suitePath, err := os.Getwd()
exitIfErr(err)
suitePath, err = filepath.Abs(suitePath)
@@ -335,6 +316,69 @@ func RunSpecs(t GinkgoTestingT, description string, args ...interface{}) bool {
return passed
}
func extractSuiteConfiguration(args []interface{}) Labels {
suiteLabels := Labels{}
configErrors := []error{}
for _, arg := range args {
switch arg := arg.(type) {
case types.SuiteConfig:
suiteConfig = arg
case types.ReporterConfig:
reporterConfig = arg
case Labels:
suiteLabels = append(suiteLabels, arg...)
default:
configErrors = append(configErrors, types.GinkgoErrors.UnknownTypePassedToRunSpecs(arg))
}
}
exitIfErrors(configErrors)
configErrors = types.VetConfig(flagSet, suiteConfig, reporterConfig)
if len(configErrors) > 0 {
fmt.Fprintf(formatter.ColorableStdErr, formatter.F("{{red}}Ginkgo detected configuration issues:{{/}}\n"))
for _, err := range configErrors {
fmt.Fprintf(formatter.ColorableStdErr, err.Error())
}
os.Exit(1)
}
return suiteLabels
}
/*
PreviewSpecs walks the testing tree and produces a report without actually invoking the specs.
See http://onsi.github.io/ginkgo/#previewing-specs for more information.
*/
func PreviewSpecs(description string, args ...any) Report {
err := global.PushClone()
if err != nil {
exitIfErr(err)
}
defer global.PopClone()
suiteLabels := extractSuiteConfiguration(args)
priorDryRun, priorParallelTotal, priorParallelProcess := suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess
suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess = true, 1, 1
defer func() {
suiteConfig.DryRun, suiteConfig.ParallelTotal, suiteConfig.ParallelProcess = priorDryRun, priorParallelTotal, priorParallelProcess
}()
reporter := reporters.NoopReporter{}
outputInterceptor = internal.NoopOutputInterceptor{}
client = nil
writer := GinkgoWriter.(*internal.Writer)
err = global.Suite.BuildTree()
exitIfErr(err)
suitePath, err := os.Getwd()
exitIfErr(err)
suitePath, err = filepath.Abs(suitePath)
exitIfErr(err)
global.Suite.Run(description, suiteLabels, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig)
return global.Suite.GetPreviewReport()
}
/*
Skip instructs Ginkgo to skip the current spec

View File

@@ -6,6 +6,7 @@ import (
var Suite *internal.Suite
var Failer *internal.Failer
var backupSuite *internal.Suite
func init() {
InitializeGlobals()
@@ -15,3 +16,13 @@ func InitializeGlobals() {
Failer = internal.NewFailer()
Suite = internal.NewSuite()
}
func PushClone() error {
var err error
backupSuite, err = Suite.Clone()
return err
}
func PopClone() {
Suite = backupSuite
}

View File

@@ -597,6 +597,12 @@ func (n Node) IsZero() bool {
/* Nodes */
type Nodes []Node
func (n Nodes) Clone() Nodes {
nodes := make(Nodes, len(n))
copy(nodes, n)
return nodes
}
func (n Nodes) CopyAppend(nodes ...Node) Nodes {
numN := len(n)
out := make(Nodes, numN+len(nodes))

View File

@@ -77,6 +77,20 @@ func NewSuite() *Suite {
}
}
func (suite *Suite) Clone() (*Suite, error) {
if suite.phase != PhaseBuildTopLevel {
return nil, fmt.Errorf("cnanot clone suite after tree has been built")
}
return &Suite{
tree: &TreeNode{},
phase: PhaseBuildTopLevel,
ProgressReporterManager: NewProgressReporterManager(),
topLevelContainers: suite.topLevelContainers.Clone(),
suiteNodes: suite.suiteNodes.Clone(),
selectiveLock: &sync.Mutex{},
}, nil
}
func (suite *Suite) BuildTree() error {
// During PhaseBuildTopLevel, the top level containers are stored in suite.topLevelCotainers and entered
// We now enter PhaseBuildTree where these top level containers are entered and added to the spec tree
@@ -328,6 +342,16 @@ func (suite *Suite) CurrentSpecReport() types.SpecReport {
return report
}
// Only valid in the preview context. In general suite.report only includes
// the specs run by _this_ node - it is only at the end of the suite that
// the parallel reports are aggregated. However in the preview context we run
// in series and
func (suite *Suite) GetPreviewReport() types.Report {
suite.selectiveLock.Lock()
defer suite.selectiveLock.Unlock()
return suite.report
}
func (suite *Suite) AddReportEntry(entry ReportEntry) error {
if suite.phase != PhaseRun {
return types.GinkgoErrors.AddReportEntryNotDuringRunPhase(entry.Location)

View File

@@ -1,3 +1,3 @@
package types
const VERSION = "2.12.1"
const VERSION = "2.13.0"

View File

@@ -1,3 +1,27 @@
## 1.28.1
### Maintenance
- Bump github.com/onsi/ginkgo/v2 from 2.12.0 to 2.13.0 [635d196]
- Bump github.com/google/go-cmp from 0.5.9 to 0.6.0 [14f8859]
- Bump golang.org/x/net from 0.14.0 to 0.17.0 [d8a6508]
- #703 doc(matchers): HaveEach() doc comment updated [2705bdb]
- Minor typos (#699) [375648c]
## 1.28.0
### Features
- Add VerifyHost handler to ghttp (#698) [0b03b36]
### Fixes
- Read Body for Newer Responses in HaveHTTPBodyMatcher (#686) [18d6673]
### Maintenance
- Bump github.com/onsi/ginkgo/v2 from 2.11.0 to 2.12.0 (#693) [55a33f3]
- Typo in matchers.go (#691) [de68e8f]
- Bump commonmarker from 0.23.9 to 0.23.10 in /docs (#690) [ab17f5e]
- chore: update test matrix for Go 1.21 (#689) [5069017]
- Bump golang.org/x/net from 0.12.0 to 0.14.0 (#688) [babe25f]
## 1.27.10
### Fixes

View File

@@ -22,7 +22,7 @@ import (
"github.com/onsi/gomega/types"
)
const GOMEGA_VERSION = "1.27.10"
const GOMEGA_VERSION = "1.28.1"
const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler.
If you're using Ginkgo then you probably forgot to put your assertion in an It().
@@ -242,7 +242,7 @@ func ExpectWithOffset(offset int, actual interface{}, extra ...interface{}) Asse
Eventually enables making assertions on asynchronous behavior.
Eventually checks that an assertion *eventually* passes. Eventually blocks when called and attempts an assertion periodically until it passes or a timeout occurs. Both the timeout and polling interval are configurable as optional arguments.
The first optional argument is the timeout (which defaults to 1s), the second is the polling interval (which defaults to 10ms). Both intervals can be specified as time.Duration, parsable duration strings or floats/integers (in which case they are interpreted as seconds). In addition an optional context.Context can be passed in - Eventually will keep trying until either the timeout epxires or the context is cancelled, whichever comes first.
The first optional argument is the timeout (which defaults to 1s), the second is the polling interval (which defaults to 10ms). Both intervals can be specified as time.Duration, parsable duration strings or floats/integers (in which case they are interpreted as seconds). In addition an optional context.Context can be passed in - Eventually will keep trying until either the timeout expires or the context is cancelled, whichever comes first.
Eventually works with any Gomega compatible matcher and supports making assertions against three categories of actual value:
@@ -313,13 +313,13 @@ It is important to note that the function passed into Eventually is invoked *syn
}).Should(BeNumerically(">=", 17))
}, SpecTimeout(time.Second))
you an also use Eventually().WithContext(ctx) to pass in the context. Passed-in contexts play nicely with paseed-in arguments as long as the context appears first. You can rewrite the above example as:
you an also use Eventually().WithContext(ctx) to pass in the context. Passed-in contexts play nicely with passed-in arguments as long as the context appears first. You can rewrite the above example as:
It("fetches the correct count", func(ctx SpecContext) {
Eventually(client.FetchCount).WithContext(ctx).WithArguments("/users").Should(BeNumerically(">=", 17))
}, SpecTimeout(time.Second))
Either way the context passd to Eventually is also passed to the underlying funciton. Now, when Ginkgo cancels the context both the FetchCount client and Gomega will be informed and can exit.
Either way the context passd to Eventually is also passed to the underlying function. Now, when Ginkgo cancels the context both the FetchCount client and Gomega will be informed and can exit.
**Category 3: Making assertions _in_ the function passed into Eventually**
@@ -349,7 +349,7 @@ For example:
will rerun the function until all assertions pass.
You can also pass additional arugments to functions that take a Gomega. The only rule is that the Gomega argument must be first. If you also want to pass the context attached to Eventually you must ensure that is the second argument. For example:
You can also pass additional arguments to functions that take a Gomega. The only rule is that the Gomega argument must be first. If you also want to pass the context attached to Eventually you must ensure that is the second argument. For example:
Eventually(func(g Gomega, ctx context.Context, path string, expected ...string){
tok, err := client.GetToken(ctx)

View File

@@ -94,7 +94,7 @@ func Succeed() types.GomegaMatcher {
//
// Expect(err).Should(MatchError("an error")) //asserts that err.Error() == "an error"
// Expect(err).Should(MatchError(SomeError)) //asserts that err == SomeError (via reflect.DeepEqual)
// Expect(err).Should(MatchError(ContainSubstring("sprocket not found"))) // asserts that edrr.Error() contains substring "sprocket not found"
// Expect(err).Should(MatchError(ContainSubstring("sprocket not found"))) // asserts that err.Error() contains substring "sprocket not found"
//
// It is an error for err to be nil or an object that does not implement the
// Error interface
@@ -381,7 +381,7 @@ func ContainElements(elements ...interface{}) types.GomegaMatcher {
}
// HaveEach succeeds if actual solely contains elements that match the passed in element.
// Please note that if actual is empty, HaveEach always will succeed.
// Please note that if actual is empty, HaveEach always will fail.
// By default HaveEach() uses Equal() to perform the match, however a
// matcher can be passed in instead:
//

View File

@@ -11,8 +11,9 @@ import (
)
type HaveHTTPBodyMatcher struct {
Expected interface{}
cachedBody []byte
Expected interface{}
cachedResponse interface{}
cachedBody []byte
}
func (matcher *HaveHTTPBodyMatcher) Match(actual interface{}) (bool, error) {
@@ -73,7 +74,7 @@ func (matcher *HaveHTTPBodyMatcher) NegatedFailureMessage(actual interface{}) (m
// the Reader is closed and it is not readable again in FailureMessage()
// or NegatedFailureMessage()
func (matcher *HaveHTTPBodyMatcher) body(actual interface{}) ([]byte, error) {
if matcher.cachedBody != nil {
if matcher.cachedResponse == actual && matcher.cachedBody != nil {
return matcher.cachedBody, nil
}
@@ -91,8 +92,10 @@ func (matcher *HaveHTTPBodyMatcher) body(actual interface{}) ([]byte, error) {
switch a := actual.(type) {
case *http.Response:
matcher.cachedResponse = a
return body(a)
case *httptest.ResponseRecorder:
matcher.cachedResponse = a
return body(a.Result())
default:
return nil, fmt.Errorf("HaveHTTPBody matcher expects *http.Response or *httptest.ResponseRecorder. Got:\n%s", format.Object(actual, 1))

View File

@@ -9,6 +9,7 @@ linters:
- exportloopref
- gci
- ginkgolinter
- gochecknoinits
- gofmt
- gofumpt
- goimports
@@ -35,6 +36,10 @@ linters-settings:
- standard
- default
- prefix(github.com/securego)
revive:
rules:
- name: dot-imports
disabled: true
run:
timeout: 5m

View File

@@ -11,7 +11,6 @@ endif
BUILDFLAGS := "-w -s -X 'main.Version=$(GIT_TAG)' -X 'main.GitTag=$(GIT_TAG)' -X 'main.BuildDate=$(BUILD_DATE)'"
CGO_ENABLED = 0
GO := GO111MODULE=on go
GO_NOMOD :=GO111MODULE=off go
GOPATH ?= $(shell $(GO) env GOPATH)
GOBIN ?= $(GOPATH)/bin
GOSEC ?= $(GOBIN)/gosec
@@ -25,15 +24,15 @@ default:
install-test-deps:
go install github.com/onsi/ginkgo/v2/ginkgo@latest
$(GO_NOMOD) get -u golang.org/x/crypto/ssh
$(GO_NOMOD) get -u github.com/lib/pq
go install golang.org/x/crypto/...@latest
go install github.com/lib/pq/...@latest
install-govulncheck:
@if [ $(GO_MINOR_VERSION) -gt $(GOVULN_MIN_VERSION) ]; then \
go install golang.org/x/vuln/cmd/govulncheck@latest; \
fi
test: install-test-deps build fmt vet sec govulncheck
test: install-test-deps build-race fmt vet sec govulncheck
$(GINKGO) -v --fail-fast
fmt:
@@ -65,6 +64,9 @@ test-coverage: install-test-deps
build:
go build -o $(BIN) ./cmd/gosec/
build-race:
go build -race -o $(BIN) ./cmd/gosec/
clean:
rm -rf build vendor dist coverage.txt
rm -f release image $(BIN)
@@ -89,5 +91,5 @@ image-push: image
tlsconfig:
go generate ./...
.PHONY: test build clean release image image-push tlsconfig

View File

@@ -1,7 +1,7 @@
# gosec - Golang Security Checker
Inspects source code for security problems by scanning the Go AST.
Inspects source code for security problems by scanning the Go AST and SSA code representation.
<img src="https://securego.io/img/gosec.png" width="320">
@@ -157,6 +157,7 @@ directory you can supply `./...` as the input argument.
- G304: File path provided as taint input
- G305: File traversal when extracting zip/tar archive
- G306: Poor file permissions used when writing to a new file
- G307: Poor file permissions used when creating a file with os.Create
- G401: Detect the usage of DES, RC4, MD5 or SHA1
- G402: Look for bad TLS connection settings
- G403: Ensure minimum RSA key length of 2048 bits
@@ -273,31 +274,33 @@ gosec -exclude-generated ./...
### Annotating code
As with all automated detection tools, there will be cases of false positives. In cases where gosec reports a failure that has been manually verified as being safe,
As with all automated detection tools, there will be cases of false positives.
In cases where gosec reports a failure that has been manually verified as being safe,
it is possible to annotate the code with a comment that starts with `#nosec`.
The `#nosec` comment should have the format `#nosec [RuleList] [-- Justification]`.
The annotation causes gosec to stop processing any further nodes within the
AST so can apply to a whole block or more granularly to a single expression.
The `#nosec` comment needs to be placed on the line where the warning is reported.
```go
func main() {
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // #nosec G402
},
}
import "md5" //#nosec
func main(){
/* #nosec */
if x > y {
h := md5.New() // this will also be ignored
}
client := &http.Client{Transport: tr}
_, err := client.Get("https://golang.org/")
if err != nil {
fmt.Println(err)
}
}
```
When a specific false positive has been identified and verified as safe, you may wish to suppress only that single rule (or a specific set of rules)
within a section of code, while continuing to scan for other problems. To do this, you can list the rule(s) to be suppressed within
When a specific false positive has been identified and verified as safe, you may
wish to suppress only that single rule (or a specific set of rules) within a section of code,
while continuing to scan for other problems. To do this, you can list the rule(s) to be suppressed within
the `#nosec` annotation, e.g: `/* #nosec G401 */` or `//#nosec G201 G202 G203`
You could put the description or justification text for the annotation. The

View File

@@ -15,6 +15,7 @@ This is a list of gosec's users. Please send a pull request with your organisati
9. [PingCAP/tidb](https://github.com/pingcap/tidb)
10. [Checkmarx](https://www.checkmarx.com/)
11. [SeatGeek](https://www.seatgeek.com/)
12. [reMarkable](https://remarkable.com)
## Projects

View File

@@ -10,7 +10,7 @@ inputs:
runs:
using: 'docker'
image: 'docker://securego/gosec:2.16.0'
image: 'docker://securego/gosec:2.18.1'
args:
- ${{ inputs.args }}

View File

@@ -57,6 +57,80 @@ const aliasOfAllRules = "*"
var generatedCodePattern = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`)
type ignore struct {
start int
end int
suppressions map[string][]issue.SuppressionInfo
}
type ignores map[string][]ignore
func newIgnores() ignores {
return make(map[string][]ignore)
}
func (i ignores) parseLine(line string) (int, int) {
parts := strings.Split(line, "-")
start, err := strconv.Atoi(parts[0])
if err != nil {
start = 0
}
end := start
if len(parts) > 1 {
if e, err := strconv.Atoi(parts[1]); err == nil {
end = e
}
}
return start, end
}
func (i ignores) add(file string, line string, suppressions map[string]issue.SuppressionInfo) {
is := []ignore{}
if _, ok := i[file]; ok {
is = i[file]
}
found := false
start, end := i.parseLine(line)
for _, ig := range is {
if ig.start <= start && ig.end >= end {
found = true
for r, s := range suppressions {
ss, ok := ig.suppressions[r]
if !ok {
ss = []issue.SuppressionInfo{}
}
ss = append(ss, s)
ig.suppressions[r] = ss
}
break
}
}
if !found {
ig := ignore{
start: start,
end: end,
suppressions: map[string][]issue.SuppressionInfo{},
}
for r, s := range suppressions {
ig.suppressions[r] = []issue.SuppressionInfo{s}
}
is = append(is, ig)
}
i[file] = is
}
func (i ignores) get(file string, line string) map[string][]issue.SuppressionInfo {
start, end := i.parseLine(line)
if is, ok := i[file]; ok {
for _, i := range is {
if i.start <= start && i.end >= end {
return i.suppressions
}
}
}
return map[string][]issue.SuppressionInfo{}
}
// The Context is populated with data parsed from the source code as it is scanned.
// It is passed through to all rule functions as they are called. Rules may use
// this data in conjunction with the encountered AST node.
@@ -69,7 +143,7 @@ type Context struct {
Root *ast.File
Imports *ImportTracker
Config Config
Ignores []map[string][]issue.SuppressionInfo
Ignores ignores
PassedValues map[string]interface{}
}
@@ -110,6 +184,7 @@ type Analyzer struct {
trackSuppressions bool
concurrency int
analyzerList []*analysis.Analyzer
mu sync.Mutex
}
// NewAnalyzer builds a new analyzer.
@@ -231,9 +306,7 @@ func (gosec *Analyzer) Process(buildTags []string, packagePaths ...string) error
return fmt.Errorf("parsing errors in pkg %q: %w", pkg.Name, err)
}
gosec.CheckRules(pkg)
if on, err := gosec.config.IsGlobalEnabled(SSA); err == nil && on {
gosec.CheckAnalyzers(pkg)
}
gosec.CheckAnalyzers(pkg)
}
}
}
@@ -252,7 +325,9 @@ func (gosec *Analyzer) load(pkgPath string, conf *packages.Config) ([]*packages.
// step 1/3 create build context.
buildD := build.Default
// step 2/3: add build tags to get env dependent files into basePackage.
gosec.mu.Lock()
buildD.BuildTags = conf.BuildFlags
gosec.mu.Unlock()
basePackage, err := buildD.ImportDir(pkgPath, build.ImportComment)
if err != nil {
return []*packages.Package{}, fmt.Errorf("importing dir %q: %w", pkgPath, err)
@@ -276,7 +351,9 @@ func (gosec *Analyzer) load(pkgPath string, conf *packages.Config) ([]*packages.
}
// step 3/3 remove build tags from conf to proceed build correctly.
gosec.mu.Lock()
conf.BuildFlags = nil
defer gosec.mu.Unlock()
pkgs, err := packages.Load(conf, packageFiles...)
if err != nil {
return []*packages.Package{}, fmt.Errorf("loading files from package %q: %w", pkgPath, err)
@@ -284,7 +361,7 @@ func (gosec *Analyzer) load(pkgPath string, conf *packages.Config) ([]*packages.
return pkgs, nil
}
// CheckRules runs analysis on the given package
// CheckRules runs analysis on the given package.
func (gosec *Analyzer) CheckRules(pkg *packages.Package) {
gosec.logger.Println("Checking package:", pkg.Name)
for _, file := range pkg.Syntax {
@@ -314,37 +391,22 @@ func (gosec *Analyzer) CheckRules(pkg *packages.Package) {
gosec.context.PkgFiles = pkg.Syntax
gosec.context.Imports = NewImportTracker()
gosec.context.PassedValues = make(map[string]interface{})
gosec.context.Ignores = newIgnores()
gosec.updateIgnores()
ast.Walk(gosec, file)
gosec.stats.NumFiles++
gosec.stats.NumLines += pkg.Fset.File(file.Pos()).LineCount()
}
}
// CheckAnalyzers runs analyzers on a given package
// CheckAnalyzers runs analyzers on a given package.
func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) {
ssaPass := &analysis.Pass{
Analyzer: buildssa.Analyzer,
Fset: pkg.Fset,
Files: pkg.Syntax,
OtherFiles: pkg.OtherFiles,
IgnoredFiles: pkg.IgnoredFiles,
Pkg: pkg.Types,
TypesInfo: pkg.TypesInfo,
TypesSizes: pkg.TypesSizes,
ResultOf: nil,
Report: nil,
ImportObjectFact: nil,
ExportObjectFact: nil,
ImportPackageFact: nil,
ExportPackageFact: nil,
AllObjectFacts: nil,
AllPackageFacts: nil,
}
ssaResult, err := ssaPass.Analyzer.Run(ssaPass)
if err != nil {
gosec.logger.Printf("Error running SSA analyser on package %q: %s", pkg.Name, err)
ssaResult, err := gosec.buildSSA(pkg)
if err != nil || ssaResult == nil {
gosec.logger.Printf("Error building the SSA representation of the package %q: %s", pkg.Name, err)
return
}
resultMap := map[*analysis.Analyzer]interface{}{
buildssa.Analyzer: &analyzers.SSAAnalyzerResult{
Config: gosec.Config(),
@@ -377,13 +439,44 @@ func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) {
continue
}
if result != nil {
if aissue, ok := result.(*issue.Issue); ok {
gosec.updateIssues(aissue, false, []issue.SuppressionInfo{})
if passIssues, ok := result.([]*issue.Issue); ok {
for _, iss := range passIssues {
gosec.updateIssues(iss)
}
}
}
}
}
// buildSSA runs the SSA pass which builds the SSA representation of the package. It handles gracefully any panic.
func (gosec *Analyzer) buildSSA(pkg *packages.Package) (interface{}, error) {
defer func() {
if r := recover(); r != nil {
gosec.logger.Printf("Panic when running SSA analyser on package: %s", pkg.Name)
}
}()
ssaPass := &analysis.Pass{
Analyzer: buildssa.Analyzer,
Fset: pkg.Fset,
Files: pkg.Syntax,
OtherFiles: pkg.OtherFiles,
IgnoredFiles: pkg.IgnoredFiles,
Pkg: pkg.Types,
TypesInfo: pkg.TypesInfo,
TypesSizes: pkg.TypesSizes,
ResultOf: nil,
Report: nil,
ImportObjectFact: nil,
ExportObjectFact: nil,
ImportPackageFact: nil,
ExportPackageFact: nil,
AllObjectFacts: nil,
AllPackageFacts: nil,
}
return ssaPass.Analyzer.Run(ssaPass)
}
func isGeneratedFile(file *ast.File) bool {
for _, comment := range file.Comments {
for _, row := range comment.List {
@@ -449,7 +542,12 @@ func (gosec *Analyzer) ignore(n ast.Node) map[string]issue.SuppressionInfo {
if groups, ok := gosec.context.Comments[n]; ok && !gosec.ignoreNosec {
// Checks if an alternative for #nosec is set and, if not, uses the default.
noSecDefaultTag := NoSecTag(string(Nosec))
noSecDefaultTag, err := gosec.config.GetGlobal(Nosec)
if err != nil {
noSecDefaultTag = NoSecTag(string(Nosec))
} else {
noSecDefaultTag = NoSecTag(noSecDefaultTag)
}
noSecAlternativeTag, err := gosec.config.GetGlobal(NoSecAlternative)
if err != nil {
noSecAlternativeTag = noSecDefaultTag
@@ -509,11 +607,6 @@ func (gosec *Analyzer) ignore(n ast.Node) map[string]issue.SuppressionInfo {
// Visit runs the gosec visitor logic over an AST created by parsing go code.
// Rule methods added with AddRule will be invoked as necessary.
func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {
ignores, ok := gosec.updateIgnoredRules(n)
if !ok {
return gosec
}
// Using ast.File instead of ast.ImportSpec, so that we can track all imports at once.
switch i := n.(type) {
case *ast.File:
@@ -521,56 +614,48 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {
}
for _, rule := range gosec.ruleset.RegisteredFor(n) {
suppressions, ignored := gosec.updateSuppressions(rule.ID(), ignores)
issue, err := rule.Match(n, gosec.context)
if err != nil {
file, line := GetLocation(n, gosec.context)
file = path.Base(file)
gosec.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line)
}
gosec.updateIssues(issue, ignored, suppressions)
gosec.updateIssues(issue)
}
return gosec
}
func (gosec *Analyzer) updateIgnoredRules(n ast.Node) (map[string][]issue.SuppressionInfo, bool) {
if n == nil {
if len(gosec.context.Ignores) > 0 {
gosec.context.Ignores = gosec.context.Ignores[1:]
}
return nil, false
func (gosec *Analyzer) updateIgnores() {
for n := range gosec.context.Comments {
gosec.updateIgnoredRulesForNode(n)
}
// Get any new rule exclusions.
ignoredRules := gosec.ignore(n)
// Now create the union of exclusions.
ignores := map[string][]issue.SuppressionInfo{}
if len(gosec.context.Ignores) > 0 {
for k, v := range gosec.context.Ignores[0] {
ignores[k] = v
}
}
for ruleID, suppression := range ignoredRules {
ignores[ruleID] = append(ignores[ruleID], suppression)
}
// Push the new set onto the stack.
gosec.context.Ignores = append([]map[string][]issue.SuppressionInfo{ignores}, gosec.context.Ignores...)
return ignores, true
}
func (gosec *Analyzer) updateSuppressions(id string, ignores map[string][]issue.SuppressionInfo) ([]issue.SuppressionInfo, bool) {
// Check if all rules are ignored.
generalSuppressions, generalIgnored := ignores[aliasOfAllRules]
// Check if the specific rule is ignored
ruleSuppressions, ruleIgnored := ignores[id]
func (gosec *Analyzer) updateIgnoredRulesForNode(n ast.Node) {
ignoredRules := gosec.ignore(n)
if len(ignoredRules) > 0 {
if gosec.context.Ignores == nil {
gosec.context.Ignores = newIgnores()
}
line := issue.GetLine(gosec.context.FileSet.File(n.Pos()), n)
gosec.context.Ignores.add(
gosec.context.FileSet.File(n.Pos()).Name(),
line,
ignoredRules,
)
}
}
func (gosec *Analyzer) getSuppressionsAtLineInFile(file string, line string, id string) ([]issue.SuppressionInfo, bool) {
ignoredRules := gosec.context.Ignores.get(file, line)
// Check if the rule was specifically suppressed at this location.
generalSuppressions, generalIgnored := ignoredRules[aliasOfAllRules]
ruleSuppressions, ruleIgnored := ignoredRules[id]
ignored := generalIgnored || ruleIgnored
suppressions := append(generalSuppressions, ruleSuppressions...)
// Track external suppressions.
// Track external suppressions of this rule.
if gosec.ruleset.IsRuleSuppressed(id) {
ignored = true
suppressions = append(suppressions, issue.SuppressionInfo{
@@ -581,8 +666,9 @@ func (gosec *Analyzer) updateSuppressions(id string, ignores map[string][]issue.
return suppressions, ignored
}
func (gosec *Analyzer) updateIssues(issue *issue.Issue, ignored bool, suppressions []issue.SuppressionInfo) {
func (gosec *Analyzer) updateIssues(issue *issue.Issue) {
if issue != nil {
suppressions, ignored := gosec.getSuppressionsAtLineInFile(issue.File, issue.Line, issue.RuleID)
if gosec.showIgnored {
issue.NoSec = ignored
}

View File

@@ -0,0 +1,386 @@
// (c) Copyright gosec's 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 analyzers
import (
"errors"
"fmt"
"go/token"
"regexp"
"strconv"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/buildssa"
"golang.org/x/tools/go/ssa"
"github.com/securego/gosec/v2/issue"
)
type bound int
const (
lowerUnbounded bound = iota
upperUnbounded
unbounded
upperBounded
)
const maxDepth = 20
func newSliceBoundsAnalyzer(id string, description string) *analysis.Analyzer {
return &analysis.Analyzer{
Name: id,
Doc: description,
Run: runSliceBounds,
Requires: []*analysis.Analyzer{buildssa.Analyzer},
}
}
func runSliceBounds(pass *analysis.Pass) (interface{}, error) {
ssaResult, err := getSSAResult(pass)
if err != nil {
return nil, err
}
issues := map[ssa.Instruction]*issue.Issue{}
ifs := map[ssa.If]*ssa.BinOp{}
for _, mcall := range ssaResult.SSA.SrcFuncs {
for _, block := range mcall.DomPreorder() {
for _, instr := range block.Instrs {
switch instr := instr.(type) {
case *ssa.Alloc:
sliceCap, err := extractSliceCapFromAlloc(instr.String())
if err != nil {
break
}
allocRefs := instr.Referrers()
if allocRefs == nil {
break
}
for _, instr := range *allocRefs {
if slice, ok := instr.(*ssa.Slice); ok {
if _, ok := slice.X.(*ssa.Alloc); ok {
if slice.Parent() != nil {
l, h := extractSliceBounds(slice)
newCap := computeSliceNewCap(l, h, sliceCap)
violations := []ssa.Instruction{}
trackSliceBounds(0, newCap, slice, &violations, ifs)
for _, s := range violations {
switch s := s.(type) {
case *ssa.Slice:
issue := newIssue(
pass.Analyzer.Name,
"slice bounds out of range",
pass.Fset,
s.Pos(),
issue.Low,
issue.High)
issues[s] = issue
case *ssa.IndexAddr:
issue := newIssue(
pass.Analyzer.Name,
"slice index out of range",
pass.Fset,
s.Pos(),
issue.Low,
issue.High)
issues[s] = issue
}
}
}
}
}
}
}
}
}
}
for ifref, binop := range ifs {
bound, value, err := extractBinOpBound(binop)
if err != nil {
continue
}
for i, block := range ifref.Block().Succs {
if i == 1 {
bound = invBound(bound)
}
for _, instr := range block.Instrs {
if _, ok := issues[instr]; ok {
switch bound {
case lowerUnbounded:
break
case upperUnbounded, unbounded:
delete(issues, instr)
case upperBounded:
switch tinstr := instr.(type) {
case *ssa.Slice:
lower, upper := extractSliceBounds(tinstr)
if isSliceInsideBounds(0, value, lower, upper) {
delete(issues, instr)
}
case *ssa.IndexAddr:
indexValue, err := extractIntValue(tinstr.Index.String())
if err != nil {
break
}
if isSliceIndexInsideBounds(0, value, indexValue) {
delete(issues, instr)
}
}
}
}
}
}
}
foundIssues := []*issue.Issue{}
for _, issue := range issues {
foundIssues = append(foundIssues, issue)
}
if len(foundIssues) > 0 {
return foundIssues, nil
}
return nil, nil
}
func trackSliceBounds(depth int, sliceCap int, slice ssa.Node, violations *[]ssa.Instruction, ifs map[ssa.If]*ssa.BinOp) {
if depth == maxDepth {
return
}
depth++
if violations == nil {
violations = &[]ssa.Instruction{}
}
referrers := slice.Referrers()
if referrers != nil {
for _, refinstr := range *referrers {
switch refinstr := refinstr.(type) {
case *ssa.Slice:
checkAllSlicesBounds(depth, sliceCap, refinstr, violations, ifs)
switch refinstr.X.(type) {
case *ssa.Alloc, *ssa.Parameter:
l, h := extractSliceBounds(refinstr)
newCap := computeSliceNewCap(l, h, sliceCap)
trackSliceBounds(depth, newCap, refinstr, violations, ifs)
}
case *ssa.IndexAddr:
indexValue, err := extractIntValue(refinstr.Index.String())
if err == nil && !isSliceIndexInsideBounds(0, sliceCap, indexValue) {
*violations = append(*violations, refinstr)
}
case *ssa.Call:
if ifref, cond := extractSliceIfLenCondition(refinstr); ifref != nil && cond != nil {
ifs[*ifref] = cond
} else {
parPos := -1
for pos, arg := range refinstr.Call.Args {
if a, ok := arg.(*ssa.Slice); ok && a == slice {
parPos = pos
}
}
if fn, ok := refinstr.Call.Value.(*ssa.Function); ok {
if len(fn.Params) > parPos && parPos > -1 {
param := fn.Params[parPos]
trackSliceBounds(depth, sliceCap, param, violations, ifs)
}
}
}
}
}
}
}
func checkAllSlicesBounds(depth int, sliceCap int, slice *ssa.Slice, violations *[]ssa.Instruction, ifs map[ssa.If]*ssa.BinOp) {
if depth == maxDepth {
return
}
depth++
if violations == nil {
violations = &[]ssa.Instruction{}
}
sliceLow, sliceHigh := extractSliceBounds(slice)
if !isSliceInsideBounds(0, sliceCap, sliceLow, sliceHigh) {
*violations = append(*violations, slice)
}
switch slice.X.(type) {
case *ssa.Alloc, *ssa.Parameter, *ssa.Slice:
l, h := extractSliceBounds(slice)
newCap := computeSliceNewCap(l, h, sliceCap)
trackSliceBounds(depth, newCap, slice, violations, ifs)
}
references := slice.Referrers()
if references == nil {
return
}
for _, ref := range *references {
switch s := ref.(type) {
case *ssa.Slice:
checkAllSlicesBounds(depth, sliceCap, s, violations, ifs)
switch s.X.(type) {
case *ssa.Alloc, *ssa.Parameter:
l, h := extractSliceBounds(s)
newCap := computeSliceNewCap(l, h, sliceCap)
trackSliceBounds(depth, newCap, s, violations, ifs)
}
}
}
}
func extractSliceIfLenCondition(call *ssa.Call) (*ssa.If, *ssa.BinOp) {
if builtInLen, ok := call.Call.Value.(*ssa.Builtin); ok {
if builtInLen.Name() == "len" {
refs := call.Referrers()
if refs != nil {
for _, ref := range *refs {
if binop, ok := ref.(*ssa.BinOp); ok {
binoprefs := binop.Referrers()
for _, ref := range *binoprefs {
if ifref, ok := ref.(*ssa.If); ok {
return ifref, binop
}
}
}
}
}
}
}
return nil, nil
}
func computeSliceNewCap(l, h, oldCap int) int {
if l == 0 && h == 0 {
return oldCap
}
if l > 0 && h == 0 {
return oldCap - l
}
if l == 0 && h > 0 {
return h
}
return h - l
}
func invBound(bound bound) bound {
switch bound {
case lowerUnbounded:
return upperUnbounded
case upperUnbounded:
return lowerUnbounded
case upperBounded:
return unbounded
case unbounded:
return upperBounded
default:
return unbounded
}
}
func extractBinOpBound(binop *ssa.BinOp) (bound, int, error) {
if binop.X != nil {
if x, ok := binop.X.(*ssa.Const); ok {
value, err := strconv.Atoi(x.Value.String())
if err != nil {
return lowerUnbounded, value, err
}
switch binop.Op {
case token.LSS, token.LEQ:
return upperUnbounded, value, nil
case token.GTR, token.GEQ:
return lowerUnbounded, value, nil
case token.EQL:
return upperBounded, value, nil
case token.NEQ:
return unbounded, value, nil
}
}
}
if binop.Y != nil {
if y, ok := binop.Y.(*ssa.Const); ok {
value, err := strconv.Atoi(y.Value.String())
if err != nil {
return lowerUnbounded, value, err
}
switch binop.Op {
case token.LSS, token.LEQ:
return lowerUnbounded, value, nil
case token.GTR, token.GEQ:
return upperUnbounded, value, nil
case token.EQL:
return upperBounded, value, nil
case token.NEQ:
return unbounded, value, nil
}
}
}
return lowerUnbounded, 0, fmt.Errorf("unable to extract constant from binop")
}
func isSliceIndexInsideBounds(l, h int, index int) bool {
return (l <= index && index < h)
}
func isSliceInsideBounds(l, h int, cl, ch int) bool {
return (l <= cl && h >= ch) && (l <= ch && h >= cl)
}
func extractSliceBounds(slice *ssa.Slice) (int, int) {
var low int
if slice.Low != nil {
l, err := extractIntValue(slice.Low.String())
if err == nil {
low = l
}
}
var high int
if slice.High != nil {
h, err := extractIntValue(slice.High.String())
if err == nil {
high = h
}
}
return low, high
}
func extractIntValue(value string) (int, error) {
parts := strings.Split(value, ":")
if len(parts) != 2 {
return 0, fmt.Errorf("invalid value: %s", value)
}
if parts[1] != "int" {
return 0, fmt.Errorf("invalid value: %s", value)
}
return strconv.Atoi(parts[0])
}
func extractSliceCapFromAlloc(instr string) (int, error) {
re := regexp.MustCompile(`new \[(\d+)\]*`)
var sliceCap int
matches := re.FindAllStringSubmatch(instr, -1)
if matches == nil {
return sliceCap, errors.New("no slice cap found")
}
if len(matches) > 0 {
m := matches[0]
if len(m) > 1 {
return strconv.Atoi(m[1])
}
}
return 0, errors.New("no slice cap found")
}

View File

@@ -1,57 +0,0 @@
// (c) Copyright gosec's 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 analyzers
import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/buildssa"
"golang.org/x/tools/go/ssa"
"github.com/securego/gosec/v2/issue"
)
func newSSRFAnalyzer(id string, description string) *analysis.Analyzer {
return &analysis.Analyzer{
Name: id,
Doc: description,
Run: runSSRF,
Requires: []*analysis.Analyzer{buildssa.Analyzer},
}
}
func runSSRF(pass *analysis.Pass) (interface{}, error) {
ssaResult, err := getSSAResult(pass)
if err != nil {
return nil, err
}
// TODO: implement the analysis
for _, fn := range ssaResult.SSA.SrcFuncs {
for _, block := range fn.DomPreorder() {
for _, instr := range block.Instrs {
switch instr := instr.(type) {
case *ssa.Call:
callee := instr.Call.StaticCallee()
if callee != nil {
ssaResult.Logger.Printf("callee: %s\n", callee)
return newIssue(pass.Analyzer.Name,
"not implemented",
pass.Fset, instr.Call.Pos(), issue.Low, issue.High), nil
}
}
}
}
}
return nil, nil
}

View File

@@ -38,7 +38,7 @@ type SSAAnalyzerResult struct {
// BuildDefaultAnalyzers returns the default list of analyzers
func BuildDefaultAnalyzers() []*analysis.Analyzer {
return []*analysis.Analyzer{
newSSRFAnalyzer("G107", "URL provided to HTTP request as taint input"),
newSliceBoundsAnalyzer("G602", "Possible slice bounds out of range"),
}
}

View File

@@ -1,7 +1,5 @@
package cwe
import "fmt"
const (
// Acronym is the acronym of CWE
Acronym = "CWE"
@@ -13,139 +11,128 @@ const (
Organization = "MITRE"
// Description the description of CWE
Description = "The MITRE Common Weakness Enumeration"
)
var (
// InformationURI link to the published CWE PDF
InformationURI = fmt.Sprintf("https://cwe.mitre.org/data/published/cwe_v%s.pdf/", Version)
InformationURI = "https://cwe.mitre.org/data/published/cwe_v" + Version + ".pdf/"
// DownloadURI link to the zipped XML of the CWE list
DownloadURI = fmt.Sprintf("https://cwe.mitre.org/data/xml/cwec_v%s.xml.zip", Version)
data = map[string]*Weakness{}
weaknesses = []*Weakness{
{
ID: "118",
Description: "The software does not restrict or incorrectly restricts operations within the boundaries of a resource that is accessed using an index or pointer, such as memory or files.",
Name: "Incorrect Access of Indexable Resource ('Range Error')",
},
{
ID: "190",
Description: "The software performs a calculation that can produce an integer overflow or wraparound, when the logic assumes that the resulting value will always be larger than the original value. This can introduce other weaknesses when the calculation is used for resource management or execution control.",
Name: "Integer Overflow or Wraparound",
},
{
ID: "200",
Description: "The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.",
Name: "Exposure of Sensitive Information to an Unauthorized Actor",
},
{
ID: "22",
Description: "The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.",
Name: "Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')",
},
{
ID: "242",
Description: "The program calls a function that can never be guaranteed to work safely.",
Name: "Use of Inherently Dangerous Function",
},
{
ID: "276",
Description: "During installation, installed file permissions are set to allow anyone to modify those files.",
Name: "Incorrect Default Permissions",
},
{
ID: "295",
Description: "The software does not validate, or incorrectly validates, a certificate.",
Name: "Improper Certificate Validation",
},
{
ID: "310",
Description: "Weaknesses in this category are related to the design and implementation of data confidentiality and integrity. Frequently these deal with the use of encoding techniques, encryption libraries, and hashing algorithms. The weaknesses in this category could lead to a degradation of the quality data if they are not addressed.",
Name: "Cryptographic Issues",
},
{
ID: "322",
Description: "The software performs a key exchange with an actor without verifying the identity of that actor.",
Name: "Key Exchange without Entity Authentication",
},
{
ID: "326",
Description: "The software stores or transmits sensitive data using an encryption scheme that is theoretically sound, but is not strong enough for the level of protection required.",
Name: "Inadequate Encryption Strength",
},
{
ID: "327",
Description: "The use of a broken or risky cryptographic algorithm is an unnecessary risk that may result in the exposure of sensitive information.",
Name: "Use of a Broken or Risky Cryptographic Algorithm",
},
{
ID: "338",
Description: "The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG's algorithm is not cryptographically strong.",
Name: "Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)",
},
{
ID: "377",
Description: "Creating and using insecure temporary files can leave application and system data vulnerable to attack.",
Name: "Insecure Temporary File",
},
{
ID: "400",
Description: "The software does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.",
Name: "Uncontrolled Resource Consumption",
},
{
ID: "409",
Description: "The software does not handle or incorrectly handles a compressed input with a very high compression ratio that produces a large output.",
Name: "Improper Handling of Highly Compressed Data (Data Amplification)",
},
{
ID: "703",
Description: "The software does not properly anticipate or handle exceptional conditions that rarely occur during normal operation of the software.",
Name: "Improper Check or Handling of Exceptional Conditions",
},
{
ID: "78",
Description: "The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.",
Name: "Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')",
},
{
ID: "79",
Description: "The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.",
Name: "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')",
},
{
ID: "798",
Description: "The software contains hard-coded credentials, such as a password or cryptographic key, which it uses for its own inbound authentication, outbound communication to external components, or encryption of internal data.",
Name: "Use of Hard-coded Credentials",
},
{
ID: "88",
Description: "The software constructs a string for a command to executed by a separate component\nin another control sphere, but it does not properly delimit the\nintended arguments, options, or switches within that command string.",
Name: "Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')",
},
{
ID: "89",
Description: "The software constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component.",
Name: "Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')",
},
{
ID: "676",
Description: "The program invokes a potentially dangerous function that could introduce a vulnerability if it is used incorrectly, but the function can also be used safely.",
Name: "Use of Potentially Dangerous Function",
},
}
DownloadURI = "https://cwe.mitre.org/data/xml/cwec_v" + Version + ".xml.zip"
)
func init() {
for _, weakness := range weaknesses {
data[weakness.ID] = weakness
}
var idWeaknesses = map[string]*Weakness{
"118": {
ID: "118",
Description: "The software does not restrict or incorrectly restricts operations within the boundaries of a resource that is accessed using an index or pointer, such as memory or files.",
Name: "Incorrect Access of Indexable Resource ('Range Error')",
},
"190": {
ID: "190",
Description: "The software performs a calculation that can produce an integer overflow or wraparound, when the logic assumes that the resulting value will always be larger than the original value. This can introduce other weaknesses when the calculation is used for resource management or execution control.",
Name: "Integer Overflow or Wraparound",
},
"200": {
ID: "200",
Description: "The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.",
Name: "Exposure of Sensitive Information to an Unauthorized Actor",
},
"22": {
ID: "22",
Description: "The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.",
Name: "Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')",
},
"242": {
ID: "242",
Description: "The program calls a function that can never be guaranteed to work safely.",
Name: "Use of Inherently Dangerous Function",
},
"276": {
ID: "276",
Description: "During installation, installed file permissions are set to allow anyone to modify those files.",
Name: "Incorrect Default Permissions",
},
"295": {
ID: "295",
Description: "The software does not validate, or incorrectly validates, a certificate.",
Name: "Improper Certificate Validation",
},
"310": {
ID: "310",
Description: "Weaknesses in this category are related to the design and implementation of data confidentiality and integrity. Frequently these deal with the use of encoding techniques, encryption libraries, and hashing algorithms. The weaknesses in this category could lead to a degradation of the quality data if they are not addressed.",
Name: "Cryptographic Issues",
},
"322": {
ID: "322",
Description: "The software performs a key exchange with an actor without verifying the identity of that actor.",
Name: "Key Exchange without Entity Authentication",
},
"326": {
ID: "326",
Description: "The software stores or transmits sensitive data using an encryption scheme that is theoretically sound, but is not strong enough for the level of protection required.",
Name: "Inadequate Encryption Strength",
},
"327": {
ID: "327",
Description: "The use of a broken or risky cryptographic algorithm is an unnecessary risk that may result in the exposure of sensitive information.",
Name: "Use of a Broken or Risky Cryptographic Algorithm",
},
"338": {
ID: "338",
Description: "The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG's algorithm is not cryptographically strong.",
Name: "Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)",
},
"377": {
ID: "377",
Description: "Creating and using insecure temporary files can leave application and system data vulnerable to attack.",
Name: "Insecure Temporary File",
},
"400": {
ID: "400",
Description: "The software does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.",
Name: "Uncontrolled Resource Consumption",
},
"409": {
ID: "409",
Description: "The software does not handle or incorrectly handles a compressed input with a very high compression ratio that produces a large output.",
Name: "Improper Handling of Highly Compressed Data (Data Amplification)",
},
"703": {
ID: "703",
Description: "The software does not properly anticipate or handle exceptional conditions that rarely occur during normal operation of the software.",
Name: "Improper Check or Handling of Exceptional Conditions",
},
"78": {
ID: "78",
Description: "The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.",
Name: "Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')",
},
"79": {
ID: "79",
Description: "The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.",
Name: "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')",
},
"798": {
ID: "798",
Description: "The software contains hard-coded credentials, such as a password or cryptographic key, which it uses for its own inbound authentication, outbound communication to external components, or encryption of internal data.",
Name: "Use of Hard-coded Credentials",
},
"88": {
ID: "88",
Description: "The software constructs a string for a command to executed by a separate component\nin another control sphere, but it does not properly delimit the\nintended arguments, options, or switches within that command string.",
Name: "Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')",
},
"89": {
ID: "89",
Description: "The software constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component.",
Name: "Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')",
},
"676": {
ID: "676",
Description: "The program invokes a potentially dangerous function that could introduce a vulnerability if it is used incorrectly, but the function can also be used safely.",
Name: "Use of Potentially Dangerous Function",
},
}
// Get Retrieves a CWE weakness by it's id
func Get(id string) *Weakness {
weakness, ok := data[id]
weakness, ok := idWeaknesses[id]
if ok && weakness != nil {
return weakness
}

View File

@@ -100,7 +100,7 @@ func GetChar(n ast.Node) (byte, error) {
// Unlike the other getters, it does _not_ raise an error for unknown ast.Node types. At the base, the recursion will hit a non-BinaryExpr type,
// either BasicLit or other, so it's not an error case. It will only error if `strconv.Unquote` errors. This matters, because there's
// currently functionality that relies on error values being returned by GetString if and when it hits a non-basiclit string node type,
// hence for cases where recursion is needed, we use this separate function, so that we can still be backwards compatbile.
// hence for cases where recursion is needed, we use this separate function, so that we can still be backwards compatible.
//
// This was added to handle a SQL injection concatenation case where the injected value is infixed between two strings, not at the start or end. See example below
//
@@ -183,7 +183,7 @@ func GetCallInfo(n ast.Node, ctx *Context) (string, string, error) {
case *ast.CallExpr:
switch call := expr.Fun.(type) {
case *ast.Ident:
if call.Name == "new" {
if call.Name == "new" && len(expr.Args) > 0 {
t := ctx.Info.TypeOf(expr.Args[0])
if t != nil {
return t.String(), fn.Sel.Name, nil

View File

@@ -178,11 +178,7 @@ func codeSnippetEndLine(node ast.Node, fobj *token.File) int64 {
// New creates a new Issue
func New(fobj *token.File, node ast.Node, ruleID, desc string, severity, confidence Score) *Issue {
name := fobj.Name()
start, end := fobj.Line(node.Pos()), fobj.Line(node.End())
line := strconv.Itoa(start)
if start != end {
line = fmt.Sprintf("%d-%d", start, end)
}
line := GetLine(fobj, node)
col := strconv.Itoa(fobj.Position(node.Pos()).Column)
var code string
@@ -217,3 +213,13 @@ func (i *Issue) WithSuppressions(suppressions []SuppressionInfo) *Issue {
i.Suppressions = suppressions
return i
}
// GetLine returns the line number of a given ast.Node
func GetLine(fobj *token.File, node ast.Node) string {
start, end := fobj.Line(node.Pos()), fobj.Line(node.End())
line := strconv.Itoa(start)
if start != end {
line = fmt.Sprintf("%d-%d", start, end)
}
return line
}

View File

@@ -5,12 +5,12 @@
<title>Golang Security Checker</title>
<link rel="shortcut icon" type="image/png" href="https://securego.io/img/favicon.png">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.9.4/css/bulma.min.css" integrity="sha512-HqxHUkJM0SYcbvxUw5P60SzdOTy/QVwA1JJrvaXJv4q7lmbDZCmZaqz01UPOaQveoxfYRv1tHozWGPMcuTBuvQ==" crossorigin="anonymous"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/default.min.css" integrity="sha512-hasIneQUHlh06VNBe7f6ZcHmeRTLIaQWFd43YriJ0UND19bvYRauxthDg8E4eVNPm9bRUhr5JGeqH7FRFXQu5g==" crossorigin="anonymous"/>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js" integrity="sha512-rdhY3cbXURo13l/WU9VlaRyaIYeJ/KBakckXIvJNAQde8DgpOmE+eZf7ha4vdqVjTtwQt69bD2wH2LXob/LB7Q==" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/go.min.js" integrity="sha512-Ea+IN1qMvgwTqOnwxM38Hn54IaDS2buEvMJNTdSB5JOT4njx3RvPij353zbUMpT+zYjDqQDr2UbrbnW5+wE54A==" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" integrity="sha512-hasIneQUHlh06VNBe7f6ZcHmeRTLIaQWFd43YriJ0UND19bvYRauxthDg8E4eVNPm9bRUhr5JGeqH7FRFXQu5g==" crossorigin="anonymous"/>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js" integrity="sha512-wsnZc3vH14xwbbaoAwkar86729DTpz6wx48ABISfmaKLZwP/lm8d7Z+Hmr9JKobAENs0qO/cGounL7LUEg10Pg==" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.7.0/react.min.js" integrity="sha512-+TFn1Gqbwx/qgwW3NU1/YtFYTfHGeD1e/8YfJZzkb6TFEZP4SUwp1Az9DMeWh3qC0F+YPKXbV3YclMUwBTvO3g==" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js" integrity="sha512-8C49ZG/SaQnWaUgCHTU1o8uIQNYE6R8me38SwF26g2Q0byEXF4Jlvm+T/JAMHMeTBiEVPslSZRv9Xt4AV0pfmw==" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.22.10/babel.min.js" integrity="sha512-UhgUmmslB4Pi7NNyzbscVQPaL3meon1WMwHwHDjCLui/kWpr2Wz4sRZ8HlG4gZIUuKkF+LWYTX55ZVPzra2HTw==" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.23.2/babel.min.js" integrity="sha512-ND11pbmp3M3Wldj90In1EUlHIt8T7O6FykizQ/yXrFjHtpX+D0SX+/IVeLjqAV91r/Lavq4BpWJIC0nUtw5Kvw==" crossorigin="anonymous"></script>
<style>
.field-label {
min-width: 80px;

View File

@@ -8,7 +8,7 @@ func NewReport(version string, schema string) *Report {
}
}
// WithRuns dafines runs for the current report
// WithRuns defines runs for the current report
func (r *Report) WithRuns(runs ...*Run) *Report {
r.Runs = runs
return r

View File

@@ -30,6 +30,7 @@ type filePermissions struct {
calls []string
}
// ID returns the ID of the rule.
func (r *filePermissions) ID() string {
return r.MetaData.ID
}
@@ -55,6 +56,7 @@ func modeIsSubset(subset int64, superset int64) bool {
return (subset | superset) == superset
}
// Match checks if the rule is matched.
func (r *filePermissions) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) {
for _, pkg := range r.pkgs {
if callexpr, matched := gosec.MatchCallByPackage(n, c, pkg, r.calls...); matched {
@@ -116,3 +118,47 @@ func NewMkdirPerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}
type osCreatePermissions struct {
issue.MetaData
mode int64
pkgs []string
calls []string
}
const defaultOsCreateMode = 0o666
// ID returns the ID of the rule.
func (r *osCreatePermissions) ID() string {
return r.MetaData.ID
}
// Match checks if the rule is matched.
func (r *osCreatePermissions) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) {
for _, pkg := range r.pkgs {
if _, matched := gosec.MatchCallByPackage(n, c, pkg, r.calls...); matched {
if !modeIsSubset(defaultOsCreateMode, r.mode) {
return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
}
}
return nil, nil
}
// NewOsCreatePerms reates a rule to detect file creation with a more permissive than configured
// permission mask.
func NewOsCreatePerms(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
mode := getConfiguredMode(conf, id, 0o666)
return &osCreatePermissions{
mode: mode,
pkgs: []string{"os"},
calls: []string{"Create"},
MetaData: issue.MetaData{
ID: id,
Severity: issue.Medium,
Confidence: issue.High,
What: fmt.Sprintf("Expect file permissions to be %#o or less but os.Create used with default permissions %#o",
mode, defaultOsCreateMode),
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@@ -15,6 +15,7 @@
package rules
import (
"fmt"
"go/ast"
"go/token"
"regexp"
@@ -26,10 +27,169 @@ import (
"github.com/securego/gosec/v2/issue"
)
type secretPattern struct {
name string
regexp *regexp.Regexp
}
var secretsPatterns = [...]secretPattern{
{
name: "RSA private key",
regexp: regexp.MustCompile(`-----BEGIN RSA PRIVATE KEY-----`),
},
{
name: "SSH (DSA) private key",
regexp: regexp.MustCompile(`-----BEGIN DSA PRIVATE KEY-----`),
},
{
name: "SSH (EC) private key",
regexp: regexp.MustCompile(`-----BEGIN EC PRIVATE KEY-----`),
},
{
name: "PGP private key block",
regexp: regexp.MustCompile(`-----BEGIN PGP PRIVATE KEY BLOCK-----`),
},
{
name: "Slack Token",
regexp: regexp.MustCompile(`xox[pborsa]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32}`),
},
{
name: "AWS API Key",
regexp: regexp.MustCompile(`AKIA[0-9A-Z]{16}`),
},
{
name: "Amazon MWS Auth Token",
regexp: regexp.MustCompile(`amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`),
},
{
name: "AWS AppSync GraphQL Key",
regexp: regexp.MustCompile(`da2-[a-z0-9]{26}`),
},
{
name: "GitHub personal access token",
regexp: regexp.MustCompile(`ghp_[a-zA-Z0-9]{36}`),
},
{
name: "GitHub fine-grained access token",
regexp: regexp.MustCompile(`github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}`),
},
{
name: "GitHub action temporary token",
regexp: regexp.MustCompile(`ghs_[a-zA-Z0-9]{36}`),
},
{
name: "Google API Key",
regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`),
},
{
name: "Google Cloud Platform API Key",
regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`),
},
{
name: "Google Cloud Platform OAuth",
regexp: regexp.MustCompile(`[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com`),
},
{
name: "Google Drive API Key",
regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`),
},
{
name: "Google Drive OAuth",
regexp: regexp.MustCompile(`[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com`),
},
{
name: "Google (GCP) Service-account",
regexp: regexp.MustCompile(`"type": "service_account"`),
},
{
name: "Google Gmail API Key",
regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`),
},
{
name: "Google Gmail OAuth",
regexp: regexp.MustCompile(`[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com`),
},
{
name: "Google OAuth Access Token",
regexp: regexp.MustCompile(`ya29\.[0-9A-Za-z\-_]+`),
},
{
name: "Google YouTube API Key",
regexp: regexp.MustCompile(`AIza[0-9A-Za-z\-_]{35}`),
},
{
name: "Google YouTube OAuth",
regexp: regexp.MustCompile(`[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com`),
},
{
name: "Generic API Key",
regexp: regexp.MustCompile(`[aA][pP][iI]_?[kK][eE][yY].*[''|"][0-9a-zA-Z]{32,45}[''|"]`),
},
{
name: "Generic Secret",
regexp: regexp.MustCompile(`[sS][eE][cC][rR][eE][tT].*[''|"][0-9a-zA-Z]{32,45}[''|"]`),
},
{
name: "Heroku API Key",
regexp: regexp.MustCompile(`[hH][eE][rR][oO][kK][uU].*[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}`),
},
{
name: "MailChimp API Key",
regexp: regexp.MustCompile(`[0-9a-f]{32}-us[0-9]{1,2}`),
},
{
name: "Mailgun API Key",
regexp: regexp.MustCompile(`key-[0-9a-zA-Z]{32}`),
},
{
name: "Password in URL",
regexp: regexp.MustCompile(`[a-zA-Z]{3,10}://[^/\\s:@]{3,20}:[^/\\s:@]{3,20}@.{1,100}["'\\s]`),
},
{
name: "Slack Webhook",
regexp: regexp.MustCompile(`https://hooks\.slack\.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}`),
},
{
name: "Stripe API Key",
regexp: regexp.MustCompile(`sk_live_[0-9a-zA-Z]{24}`),
},
{
name: "Stripe API Key",
regexp: regexp.MustCompile(`sk_live_[0-9a-zA-Z]{24}`),
},
{
name: "Stripe Restricted API Key",
regexp: regexp.MustCompile(`rk_live_[0-9a-zA-Z]{24}`),
},
{
name: "Square Access Token",
regexp: regexp.MustCompile(`sq0atp-[0-9A-Za-z\-_]{22}`),
},
{
name: "Square OAuth Secret",
regexp: regexp.MustCompile(`sq0csp-[0-9A-Za-z\-_]{43}`),
},
{
name: "Telegram Bot API Key",
regexp: regexp.MustCompile(`[0-9]+:AA[0-9A-Za-z\-_]{33}`),
},
{
name: "Twilio API Key",
regexp: regexp.MustCompile(`SK[0-9a-fA-F]{32}`),
},
{
name: "Twitter Access Token",
regexp: regexp.MustCompile(`[tT][wW][iI][tT][tT][eE][rR].*[1-9][0-9]+-[0-9a-zA-Z]{40}`),
},
{
name: "Twitter OAuth",
regexp: regexp.MustCompile(`[tT][wW][iI][tT][tT][eE][rR].*[''|"][0-9a-zA-Z]{35,44}[''|"]`),
},
}
type credentials struct {
issue.MetaData
pattern *regexp.Regexp
patternValue *regexp.Regexp // Pattern for matching string values (LHS on assign statements)
entropyThreshold float64
perCharThreshold float64
truncate int
@@ -56,6 +216,15 @@ func (r *credentials) isHighEntropyString(str string) bool {
entropyPerChar >= r.perCharThreshold))
}
func (r *credentials) isSecretPattern(str string) (bool, string) {
for _, pattern := range secretsPatterns {
if pattern.regexp.MatchString(str) {
return true, pattern.name
}
}
return false, ""
}
func (r *credentials) Match(n ast.Node, ctx *gosec.Context) (*issue.Issue, error) {
switch node := n.(type) {
case *ast.AssignStmt:
@@ -89,9 +258,9 @@ func (r *credentials) matchAssign(assign *ast.AssignStmt, ctx *gosec.Context) (*
continue
}
if r.patternValue.MatchString(val) {
if r.ignoreEntropy || r.isHighEntropyString(val) {
return ctx.NewIssue(assign, r.ID(), r.What, r.Severity, r.Confidence), nil
if r.ignoreEntropy || r.isHighEntropyString(val) {
if ok, patternName := r.isSecretPattern(val); ok {
return ctx.NewIssue(assign, r.ID(), fmt.Sprintf("%s: %s", r.What, patternName), r.Severity, r.Confidence), nil
}
}
}
@@ -120,9 +289,9 @@ func (r *credentials) matchValueSpec(valueSpec *ast.ValueSpec, ctx *gosec.Contex
// Now that no variable names have been matched, match the actual values to find any creds
for _, ident := range valueSpec.Values {
if val, err := gosec.GetString(ident); err == nil {
if r.patternValue.MatchString(val) {
if r.ignoreEntropy || r.isHighEntropyString(val) {
return ctx.NewIssue(valueSpec, r.ID(), r.What, r.Severity, r.Confidence), nil
if r.ignoreEntropy || r.isHighEntropyString(val) {
if ok, patternName := r.isSecretPattern(val); ok {
return ctx.NewIssue(valueSpec, r.ID(), fmt.Sprintf("%s: %s", r.What, patternName), r.Severity, r.Confidence), nil
}
}
}
@@ -159,9 +328,9 @@ func (r *credentials) matchEqualityCheck(binaryExpr *ast.BinaryExpr, ctx *gosec.
if ok && identStrConst.Kind == token.STRING {
s, _ := gosec.GetString(identStrConst)
if r.patternValue.MatchString(s) {
if r.ignoreEntropy || r.isHighEntropyString(s) {
return ctx.NewIssue(binaryExpr, r.ID(), r.What, r.Severity, r.Confidence), nil
if r.ignoreEntropy || r.isHighEntropyString(s) {
if ok, patternName := r.isSecretPattern(s); ok {
return ctx.NewIssue(binaryExpr, r.ID(), fmt.Sprintf("%s: %s", r.What, patternName), r.Severity, r.Confidence), nil
}
}
}
@@ -173,7 +342,6 @@ func (r *credentials) matchEqualityCheck(binaryExpr *ast.BinaryExpr, ctx *gosec.
// assigned to variables that appear to be related to credentials.
func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
pattern := `(?i)passwd|pass|password|pwd|secret|token|pw|apiKey|bearer|cred`
patternValue := "(?i)(^(.*[:;,](\\s)*)?[a-f0-9]{64}$)|(AIza[0-9A-Za-z-_]{35})|(^(.*[:;,](\\s)*)?github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$)|(^(.*[:;,](\\s)*)?[0-9a-zA-Z-_]{24}$)"
entropyThreshold := 80.0
perCharThreshold := 3.0
ignoreEntropy := false
@@ -186,12 +354,6 @@ func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.No
}
}
if configPatternValue, ok := conf["patternValue"]; ok {
if cfgPatternValue, ok := configPatternValue.(string); ok {
patternValue = cfgPatternValue
}
}
if configIgnoreEntropy, ok := conf["ignore_entropy"]; ok {
if cfgIgnoreEntropy, ok := configIgnoreEntropy.(bool); ok {
ignoreEntropy = cfgIgnoreEntropy
@@ -222,7 +384,6 @@ func NewHardcodedCredentials(id string, conf gosec.Config) (gosec.Rule, []ast.No
return &credentials{
pattern: regexp.MustCompile(pattern),
patternValue: regexp.MustCompile(patternValue),
entropyThreshold: entropyThreshold,
perCharThreshold: perCharThreshold,
ignoreEntropy: ignoreEntropy,

View File

@@ -3,6 +3,7 @@ package rules
import (
"go/ast"
"go/token"
"go/types"
"github.com/securego/gosec/v2"
"github.com/securego/gosec/v2/issue"
@@ -28,23 +29,20 @@ func containsUnary(exprs []*ast.UnaryExpr, expr *ast.UnaryExpr) bool {
return false
}
func getIdentExpr(expr ast.Expr) *ast.Ident {
func getIdentExpr(expr ast.Expr) (*ast.Ident, bool) {
return doGetIdentExpr(expr, false)
}
func doGetIdentExpr(expr ast.Expr, hasSelector bool) (*ast.Ident, bool) {
switch node := expr.(type) {
case *ast.Ident:
return node
return node, hasSelector
case *ast.SelectorExpr:
return getIdentExpr(node.X)
return doGetIdentExpr(node.X, true)
case *ast.UnaryExpr:
switch e := node.X.(type) {
case *ast.Ident:
return e
case *ast.SelectorExpr:
return getIdentExpr(e.X)
default:
return nil
}
return doGetIdentExpr(node.X, hasSelector)
default:
return nil
return nil, false
}
}
@@ -92,9 +90,13 @@ func (r *implicitAliasing) Match(n ast.Node, c *gosec.Context) (*issue.Issue, er
}
// If we find a unary op of & (reference) of an object within r.aliases, complain.
if identExpr := getIdentExpr(node); identExpr != nil && node.Op.String() == "&" {
if identExpr, hasSelector := getIdentExpr(node); identExpr != nil && node.Op.String() == "&" {
if _, contains := r.aliases[identExpr.Obj]; contains {
return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil
_, isPointer := c.Info.TypeOf(identExpr).(*types.Pointer)
if !hasSelector || !isPointer {
return c.NewIssue(n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
}
}
case *ast.ReturnStmt:

View File

@@ -91,6 +91,7 @@ func Generate(trackSuppressions bool, filters ...RuleFilter) RuleList {
{"G304", "File path provided as taint input", NewReadFile},
{"G305", "File path traversal when extracting zip archive", NewArchive},
{"G306", "Poor file permissions used when writing to a file", NewWritePerms},
{"G307", "Poor file permissions used when creating a file with os.Create", NewOsCreatePerms},
// crypto
{"G401", "Detect the usage of DES, RC4, MD5 or SHA1", NewUsesWeakCryptography},
@@ -107,7 +108,6 @@ func Generate(trackSuppressions bool, filters ...RuleFilter) RuleList {
// memory safety
{"G601", "Implicit memory aliasing in RangeStmt", NewImplicitAliasing},
{"G602", "Slice access out of bounds", NewSliceBoundCheck},
}
ruleMap := make(map[string]RuleDefinition)

View File

@@ -1,405 +0,0 @@
package rules
import (
"fmt"
"go/ast"
"go/types"
"github.com/securego/gosec/v2"
"github.com/securego/gosec/v2/issue"
)
// sliceOutOfBounds is a rule which checks for slices which are accessed outside their capacity,
// either through indexing it out of bounds or through slice expressions whose low or high index
// are out of bounds.
type sliceOutOfBounds struct {
sliceCaps map[*ast.CallExpr]map[string]*int64 // Capacities of slices. Maps function call -> var name -> value.
currentScope *types.Scope // Current scope. Map is cleared when scope changes.
currentFuncName string // Current function.
funcCallArgs map[string][]*int64 // Caps to load once a func declaration is scanned.
issue.MetaData // Metadata for this rule.
}
// ID returns the rule ID for sliceOutOfBounds: G602.
func (s *sliceOutOfBounds) ID() string {
return s.MetaData.ID
}
func (s *sliceOutOfBounds) Match(node ast.Node, ctx *gosec.Context) (*issue.Issue, error) {
if s.currentScope == nil {
s.currentScope = ctx.Pkg.Scope()
} else if s.currentScope != ctx.Pkg.Scope() {
s.currentScope = ctx.Pkg.Scope()
// Clear slice map, since we are in a new scope
sliceMapNil := make(map[string]*int64)
sliceCaps := make(map[*ast.CallExpr]map[string]*int64)
sliceCaps[nil] = sliceMapNil
s.sliceCaps = sliceCaps
}
switch node := node.(type) {
case *ast.AssignStmt:
return s.matchAssign(node, ctx)
case *ast.SliceExpr:
return s.matchSliceExpr(node, ctx)
case *ast.IndexExpr:
return s.matchIndexExpr(node, ctx)
case *ast.FuncDecl:
s.currentFuncName = node.Name.Name
s.loadArgCaps(node)
case *ast.CallExpr:
if _, ok := node.Fun.(*ast.FuncLit); ok {
// Do nothing with func literals for now.
break
}
sliceMap := make(map[string]*int64)
s.sliceCaps[node] = sliceMap
s.setupCallArgCaps(node, ctx)
}
return nil, nil
}
// updateSliceCaps takes in a variable name and a map of calls we are updating the variables for to the updated values
// and will add it to the sliceCaps map.
func (s *sliceOutOfBounds) updateSliceCaps(varName string, caps map[*ast.CallExpr]*int64) {
for callExpr, cap := range caps {
s.sliceCaps[callExpr][varName] = cap
}
}
// getAllCalls returns all CallExprs that are calls to the given function.
func (s *sliceOutOfBounds) getAllCalls(funcName string, ctx *gosec.Context) []*ast.CallExpr {
calls := []*ast.CallExpr{}
for callExpr := range s.sliceCaps {
if callExpr != nil {
// Compare the names of the function the code is scanning with the current call we are iterating over
_, callFuncName, err := gosec.GetCallInfo(callExpr, ctx)
if err != nil {
continue
}
if callFuncName == funcName {
calls = append(calls, callExpr)
}
}
}
return calls
}
// getSliceCapsForFunc gets all the capacities for slice with given name that are stored for each call to the passed function.
func (s *sliceOutOfBounds) getSliceCapsForFunc(funcName string, varName string, ctx *gosec.Context) map[*ast.CallExpr]*int64 {
caps := make(map[*ast.CallExpr]*int64)
calls := s.getAllCalls(funcName, ctx)
for _, call := range calls {
if callCaps, ok := s.sliceCaps[call]; ok {
caps[call] = callCaps[varName]
}
}
return caps
}
// setupCallArgCaps evaluates and saves the caps for any slices in the args so they can be validated when the function is scanned.
func (s *sliceOutOfBounds) setupCallArgCaps(callExpr *ast.CallExpr, ctx *gosec.Context) {
// Array of caps to be loaded once the function declaration is scanned
funcCallArgs := []*int64{}
// Get function name
_, funcName, err := gosec.GetCallInfo(callExpr, ctx)
if err != nil {
return
}
for _, arg := range callExpr.Args {
switch node := arg.(type) {
case *ast.SliceExpr:
caps := s.evaluateSliceExpr(node, ctx)
// Simplifying assumption: use the lowest capacity. Storing all possible capacities for slices passed
// to a function call would catch the most issues, but would require a data structure like a stack and a
// reworking of the code for scanning itself. Use the lowest capacity, as this would be more likely to
// raise an issue for being out of bounds.
var lowestCap *int64
for _, cap := range caps {
if cap == nil {
continue
}
if lowestCap == nil {
lowestCap = cap
} else if *lowestCap > *cap {
lowestCap = cap
}
}
if lowestCap == nil {
funcCallArgs = append(funcCallArgs, nil)
continue
}
// Now create a map of just this value to add it to the sliceCaps
funcCallArgs = append(funcCallArgs, lowestCap)
case *ast.Ident:
ident := arg.(*ast.Ident)
caps := s.getSliceCapsForFunc(s.currentFuncName, ident.Name, ctx)
var lowestCap *int64
for _, cap := range caps {
if cap == nil {
continue
}
if lowestCap == nil {
lowestCap = cap
} else if *lowestCap > *cap {
lowestCap = cap
}
}
if lowestCap == nil {
funcCallArgs = append(funcCallArgs, nil)
continue
}
// Now create a map of just this value to add it to the sliceCaps
funcCallArgs = append(funcCallArgs, lowestCap)
default:
funcCallArgs = append(funcCallArgs, nil)
}
}
s.funcCallArgs[funcName] = funcCallArgs
}
// loadArgCaps loads caps that were saved for a call to this function.
func (s *sliceOutOfBounds) loadArgCaps(funcDecl *ast.FuncDecl) {
sliceMap := make(map[string]*int64)
funcName := funcDecl.Name.Name
// Create a dummmy call expr for the new function. This is so we can still store args for
// functions which are not explicitly called in the code by other functions (specifically, main).
ident := ast.NewIdent(funcName)
dummyCallExpr := ast.CallExpr{
Fun: ident,
}
argCaps, ok := s.funcCallArgs[funcName]
if !ok || len(argCaps) == 0 {
s.sliceCaps[&dummyCallExpr] = sliceMap
return
}
params := funcDecl.Type.Params.List
if len(params) > len(argCaps) {
return // Length of params and args doesn't match, so don't do anything with this.
}
for it := range params {
capacity := argCaps[it]
if capacity == nil {
continue
}
if len(params[it].Names) == 0 {
continue
}
if paramName := params[it].Names[0]; paramName != nil {
sliceMap[paramName.Name] = capacity
}
}
s.sliceCaps[&dummyCallExpr] = sliceMap
}
// matchSliceMake matches calls to make() and stores the capacity of the new slice in the map to compare against future slice usage.
func (s *sliceOutOfBounds) matchSliceMake(funcCall *ast.CallExpr, sliceName string, ctx *gosec.Context) (*issue.Issue, error) {
_, funcName, err := gosec.GetCallInfo(funcCall, ctx)
if err != nil || funcName != "make" {
return nil, nil
}
var capacityArg int
if len(funcCall.Args) < 2 {
return nil, nil // No size passed
} else if len(funcCall.Args) == 2 {
capacityArg = 1
} else if len(funcCall.Args) == 3 {
capacityArg = 2
} else {
return nil, nil // Unexpected, args should always be 2 or 3
}
// Check and get the capacity of the slice passed to make. It must be a literal value, since we aren't evaluating the expression.
sliceCapLit, ok := funcCall.Args[capacityArg].(*ast.BasicLit)
if !ok {
return nil, nil
}
capacity, err := gosec.GetInt(sliceCapLit)
if err != nil {
return nil, nil
}
caps := s.getSliceCapsForFunc(s.currentFuncName, sliceName, ctx)
for callExpr := range caps {
caps[callExpr] = &capacity
}
s.updateSliceCaps(sliceName, caps)
return nil, nil
}
// evaluateSliceExpr takes a slice expression and evaluates what the capacity of said slice is for each of the
// calls to the current function. Returns map of the call expressions of each call to the current function to
// the evaluated capacities.
func (s *sliceOutOfBounds) evaluateSliceExpr(node *ast.SliceExpr, ctx *gosec.Context) map[*ast.CallExpr]*int64 {
// Get ident to get name
ident, ok := node.X.(*ast.Ident)
if !ok {
return nil
}
// Get cap of old slice to calculate this new slice's cap
caps := s.getSliceCapsForFunc(s.currentFuncName, ident.Name, ctx)
for callExpr, oldCap := range caps {
if oldCap == nil {
continue
}
// Get and check low value
lowIdent, ok := node.Low.(*ast.BasicLit)
if ok && lowIdent != nil {
low, _ := gosec.GetInt(lowIdent)
newCap := *oldCap - low
caps[callExpr] = &newCap
} else if lowIdent == nil { // If no lower bound, capacity will be same
continue
}
}
return caps
}
// matchSliceAssignment matches slice assignments, calculates capacity of slice if possible to store it in map.
func (s *sliceOutOfBounds) matchSliceAssignment(node *ast.SliceExpr, sliceName string, ctx *gosec.Context) (*issue.Issue, error) {
// First do the normal match that verifies the slice expr is not out of bounds
if i, err := s.matchSliceExpr(node, ctx); err != nil {
return i, fmt.Errorf("There was an error while matching a slice expression to check slice bounds for %s: %w", sliceName, err)
}
// Now that the assignment is (presumably) successfully, we can calculate the capacity and add this new slice to the map
caps := s.evaluateSliceExpr(node, ctx)
s.updateSliceCaps(sliceName, caps)
return nil, nil
}
// matchAssign matches checks if an assignment statement is making a slice, or if it is assigning a slice.
func (s *sliceOutOfBounds) matchAssign(node *ast.AssignStmt, ctx *gosec.Context) (*issue.Issue, error) {
// Check RHS for calls to make() so we can get the actual size of the slice
for it, i := range node.Rhs {
// Get the slice name so we can associate the cap with the slice in the map
sliceIdent, ok := node.Lhs[it].(*ast.Ident)
if !ok {
return nil, nil
}
sliceName := sliceIdent.Name
switch expr := i.(type) {
case *ast.CallExpr: // Check for and handle call to make()
return s.matchSliceMake(expr, sliceName, ctx)
case *ast.SliceExpr: // Handle assignments to a slice
return s.matchSliceAssignment(expr, sliceName, ctx)
}
}
return nil, nil
}
// matchSliceExpr validates that a given slice expression (eg, slice[10:30]) is not out of bounds.
func (s *sliceOutOfBounds) matchSliceExpr(node *ast.SliceExpr, ctx *gosec.Context) (*issue.Issue, error) {
// First get the slice name so we can check the size in our map
ident, ok := node.X.(*ast.Ident)
if !ok {
return nil, nil
}
// Get slice cap from the map to compare it against high and low
caps := s.getSliceCapsForFunc(s.currentFuncName, ident.Name, ctx)
for _, cap := range caps {
if cap == nil {
continue
}
// Get and check high value
highIdent, ok := node.High.(*ast.BasicLit)
if ok && highIdent != nil {
high, _ := gosec.GetInt(highIdent)
if high > *cap {
return ctx.NewIssue(node, s.ID(), s.What, s.Severity, s.Confidence), nil
}
}
// Get and check low value
lowIdent, ok := node.Low.(*ast.BasicLit)
if ok && lowIdent != nil {
low, _ := gosec.GetInt(lowIdent)
if low > *cap {
return ctx.NewIssue(node, s.ID(), s.What, s.Severity, s.Confidence), nil
}
}
}
return nil, nil
}
// matchIndexExpr validates that an index into a slice is not out of bounds.
func (s *sliceOutOfBounds) matchIndexExpr(node *ast.IndexExpr, ctx *gosec.Context) (*issue.Issue, error) {
// First get the slice name so we can check the size in our map
ident, ok := node.X.(*ast.Ident)
if !ok {
return nil, nil
}
// Get slice cap from the map to compare it against high and low
caps := s.getSliceCapsForFunc(s.currentFuncName, ident.Name, ctx)
for _, cap := range caps {
if cap == nil {
continue
}
// Get the index literal
indexIdent, ok := node.Index.(*ast.BasicLit)
if ok && indexIdent != nil {
index, _ := gosec.GetInt(indexIdent)
if index >= *cap {
return ctx.NewIssue(node, s.ID(), s.What, s.Severity, s.Confidence), nil
}
}
}
return nil, nil
}
// NewSliceBoundCheck attempts to find any slices being accessed out of bounds
// by reslicing or by being indexed.
func NewSliceBoundCheck(id string, _ gosec.Config) (gosec.Rule, []ast.Node) {
sliceMap := make(map[*ast.CallExpr]map[string]*int64)
return &sliceOutOfBounds{
sliceCaps: sliceMap,
currentFuncName: "",
funcCallArgs: make(map[string][]*int64),
MetaData: issue.MetaData{
ID: id,
Severity: issue.Medium,
Confidence: issue.Medium,
What: "Potentially accessing slice out of bounds",
},
}, []ast.Node{(*ast.CallExpr)(nil), (*ast.FuncDecl)(nil), (*ast.AssignStmt)(nil), (*ast.SliceExpr)(nil), (*ast.IndexExpr)(nil)}
}

View File

@@ -43,7 +43,7 @@ func (r *usingUnsafe) Match(n ast.Node, c *gosec.Context) (gi *issue.Issue, err
func NewUsingUnsafe(id string, _ gosec.Config) (gosec.Rule, []ast.Node) {
return &usingUnsafe{
pkg: "unsafe",
calls: []string{"Alignof", "Offsetof", "Sizeof", "Pointer"},
calls: []string{"Pointer", "String", "StringData", "Slice", "SliceData"},
MetaData: issue.MetaData{
ID: id,
What: "Use of unsafe calls should be audited",

View File

@@ -367,7 +367,7 @@ func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, a
}
}
if !fixed {
errorf("invalid go version '%s': must match format 1.23", args[0])
errorf("invalid go version '%s': must match format 1.23.0", args[0])
return
}
}
@@ -384,7 +384,7 @@ func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, a
errorf("toolchain directive expects exactly one argument")
return
} else if strict && !ToolchainRE.MatchString(args[0]) {
errorf("invalid toolchain version '%s': must match format go1.23 or local", args[0])
errorf("invalid toolchain version '%s': must match format go1.23.0 or local", args[0])
return
}
f.Toolchain = &Toolchain{Syntax: line}

View File

@@ -188,6 +188,8 @@ type Generator struct {
trimPrefix string
lineComment bool
logf func(format string, args ...interface{}) // test logging hook; nil when not testing
}
func (g *Generator) Printf(format string, args ...interface{}) {
@@ -221,13 +223,14 @@ func (g *Generator) parsePackage(patterns []string, tags []string) {
// in a separate pass? For later.
Tests: false,
BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))},
Logf: g.logf,
}
pkgs, err := packages.Load(cfg, patterns...)
if err != nil {
log.Fatal(err)
}
if len(pkgs) != 1 {
log.Fatalf("error: %d packages found", len(pkgs))
log.Fatalf("error: %d packages matching %v", len(pkgs), strings.Join(patterns, " "))
}
g.addPackage(pkgs[0])
}

View File

@@ -191,7 +191,7 @@ and buildtag, inspect the raw text of Go source files or even non-Go
files such as assembly. To report a diagnostic against a line of a
raw text file, use the following sequence:
content, err := ioutil.ReadFile(filename)
content, err := os.ReadFile(filename)
if err != nil { ... }
tf := fset.AddFile(filename, -1, len(content))
tf.SetLinesForContent(content)

View File

@@ -35,7 +35,7 @@ The Package struct provides basic information about the package, including
- Imports, a map from source import strings to the Packages they name;
- Types, the type information for the package's exported symbols;
- Syntax, the parsed syntax trees for the package's source code; and
- TypeInfo, the result of a complete type-check of the package syntax trees.
- TypesInfo, the result of a complete type-check of the package syntax trees.
(See the documentation for type Package for the complete list of fields
and more detailed descriptions.)

View File

@@ -9,7 +9,6 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"path"
@@ -1109,7 +1108,7 @@ func (state *golistState) writeOverlays() (filename string, cleanup func(), err
if len(state.cfg.Overlay) == 0 {
return "", func() {}, nil
}
dir, err := ioutil.TempDir("", "gopackages-*")
dir, err := os.MkdirTemp("", "gopackages-*")
if err != nil {
return "", nil, err
}
@@ -1128,7 +1127,7 @@ func (state *golistState) writeOverlays() (filename string, cleanup func(), err
// Create a unique filename for the overlaid files, to avoid
// creating nested directories.
noSeparator := strings.Join(strings.Split(filepath.ToSlash(k), "/"), "")
f, err := ioutil.TempFile(dir, fmt.Sprintf("*-%s", noSeparator))
f, err := os.CreateTemp(dir, fmt.Sprintf("*-%s", noSeparator))
if err != nil {
return "", func() {}, err
}
@@ -1146,7 +1145,7 @@ func (state *golistState) writeOverlays() (filename string, cleanup func(), err
}
// Write out the overlay file that contains the filepath mappings.
filename = filepath.Join(dir, "overlay.json")
if err := ioutil.WriteFile(filename, b, 0665); err != nil {
if err := os.WriteFile(filename, b, 0665); err != nil {
return "", func() {}, err
}
return filename, cleanup, nil

View File

@@ -16,7 +16,6 @@ import (
"go/token"
"go/types"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
@@ -1127,7 +1126,7 @@ func (ld *loader) parseFile(filename string) (*ast.File, error) {
var err error
if src == nil {
ioLimit <- true // wait
src, err = ioutil.ReadFile(filename)
src, err = os.ReadFile(filename)
<-ioLimit // signal
}
if err != nil {

View File

@@ -29,7 +29,6 @@ import (
"go/token"
"go/types"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -221,7 +220,7 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func
switch hdr {
case "$$B\n":
var data []byte
data, err = ioutil.ReadAll(buf)
data, err = io.ReadAll(buf)
if err != nil {
break
}

16
vendor/modules.txt generated vendored
View File

@@ -449,7 +449,7 @@ github.com/google/pprof/profile
# github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
## explicit; go 1.13
github.com/google/shlex
# github.com/google/uuid v1.3.0
# github.com/google/uuid v1.3.1
## explicit
github.com/google/uuid
# github.com/gookit/color v1.5.4
@@ -603,7 +603,7 @@ github.com/onsi/ginkgo/formatter
github.com/onsi/ginkgo/reporters
github.com/onsi/ginkgo/reporters/stenographer
github.com/onsi/ginkgo/types
# github.com/onsi/ginkgo/v2 v2.12.1
# github.com/onsi/ginkgo/v2 v2.13.0
## explicit; go 1.18
github.com/onsi/ginkgo/v2
github.com/onsi/ginkgo/v2/config
@@ -625,7 +625,7 @@ github.com/onsi/ginkgo/v2/internal/parallel_support
github.com/onsi/ginkgo/v2/internal/testingtproxy
github.com/onsi/ginkgo/v2/reporters
github.com/onsi/ginkgo/v2/types
# github.com/onsi/gomega v1.27.10
# github.com/onsi/gomega v1.28.1
## explicit; go 1.18
github.com/onsi/gomega
github.com/onsi/gomega/format
@@ -779,7 +779,7 @@ github.com/russross/blackfriday
# github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
## explicit; go 1.13
github.com/sabhiram/go-gitignore
# github.com/securego/gosec/v2 v2.17.0
# github.com/securego/gosec/v2 v2.18.2
## explicit; go 1.20
github.com/securego/gosec/v2
github.com/securego/gosec/v2/analyzers
@@ -898,8 +898,8 @@ golang.org/x/crypto/ssh
golang.org/x/crypto/ssh/agent
golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
golang.org/x/crypto/ssh/knownhosts
# golang.org/x/mod v0.12.0
## explicit; go 1.17
# golang.org/x/mod v0.13.0
## explicit; go 1.18
golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile
golang.org/x/mod/module
@@ -921,7 +921,7 @@ golang.org/x/net/proxy
## explicit; go 1.17
golang.org/x/oauth2
golang.org/x/oauth2/internal
# golang.org/x/sync v0.3.0
# golang.org/x/sync v0.4.0
## explicit; go 1.17
golang.org/x/sync/errgroup
golang.org/x/sync/semaphore
@@ -964,7 +964,7 @@ golang.org/x/text/width
# golang.org/x/time v0.3.0
## explicit
golang.org/x/time/rate
# golang.org/x/tools v0.13.0
# golang.org/x/tools v0.14.0
## explicit; go 1.18
golang.org/x/tools/cmd/stringer
golang.org/x/tools/go/analysis